Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Prevent exception when attempting to write PaxTarEntry obtained from TarReader by carlossanlop · Pull Request #75237 · dotnet/runtime · GitHub
Skip to content

Prevent exception when attempting to write PaxTarEntry obtained from TarReader - #75237

Closed
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect
Closed

Prevent exception when attempting to write PaxTarEntry obtained from TarReader#75237
carlossanlop wants to merge 8 commits into
dotnet:mainfrom
carlossanlop:ExtendedAttributesCollect

Conversation

@carlossanlop

Copy link
Copy Markdown
Contributor

Fixes#75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

@ghost

ghost commented Sep 8, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #75215

A PaxTarEntry obtained from a TarReader will have an extended attributes dictionary filled with the fields that get collected by default.

When attempting to write such entry to a TarWriter via WriteEntry(TarEntry), we collect the default extended attribute fields and store them in the dictionary. The problem is that we were using Dictionary.Add, which throws if the key already exists, and that is not the intended behavior.

The fix is to add the key using the indexer. That way, we always write the most up-to-date value to the dictionary.

We should try to get it into 7.0 as mentioned in the issue.

Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:-

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 45 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind elaborate further why are you skipping these archives? and how do you deal with them in other tests?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two, the reason is the same as the one I wrote a few lines above.

Generally speaking, these tests are collecting the unarchived files from the filesystem. We have to skip them because either some OS variants do not support the files in these test cases, or nuget is unable to pack them from runtime-assets and unpack them unchanged into the test data.

Other tests are not consuming the unarchived files, they are consuming the .tar or .tar.gz files directly, which we can iterate without interacting with the filesystem. But this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

The skipped test cases aren't that important. What's important is the copy of an entry that comes from a TarReader into a new TarWriter, particularly in the PAX format, to verify the bug is fixed in the extended attributes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test in particular needs to be able to compare with the filesystem, not with TarReader entries.

I left a suggestion below that can help you avoid comparing against the filesystem #75237 (comment). Could that help removing all these continues?

Comment threadsrc/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.cs Outdated
Comment on lines 252 to 250

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way you can validate the copy is by re-iterating originArchive (along with destinationArchive) and comparing as much fields as possible against the entries in destinationReader.

@carlossanlopcarlossanlopSep 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have both tests? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see value in extracting the entries and then using them for comparison in this case, the reported error is when you copy a Tar to another without writing to the disk. IMO, the filesystem should not be involved at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is a bug in the way we are reading information with the Tar APIs, then comparing the items read with the first TarReader with a second TarReader will give me the same results. The utf8 case discussed above is a good example of this.

@jozkeejozkeeSep 8, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there should be a separate test that validates file system entries extracted to disk against tar archive entries. Said test should not exercise the "Copy tar" scenario, just "extract to disk then compare"

Comment threadsrc/libraries/System.Formats.Tar/tests/TarTestsBase.cs Outdated
Comment on lines 626 to 629

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior looks like it was intentional, is it OK that we change it from "add if it was absent" to "always set"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CollectExtendedAttributesFromStandardFields method does exactly what other tar tools do when creating a PAX entry:

  • They always insert the values of name and mtime for all entry types, regardless if they fit or not in the standard field.
  • They insert linkname for symlinks and hardlinks if not empty, regardless if it fits or not in the standard field.
  • If gname or uname are set, they get added if their byte lengths are too large for the standard field.
  • If size does not fit in the standard field when converted to string, it gets inserted.

If the user decides to manually change the mtime before writing it into the TarWriter, then the mtime would not be updated.

I need to add more unit tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

Comment threadsrc/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

We should try to get it into 7.0 as mentioned in the issue.

This hasn't moved in 10 days. What's the plan?

@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

This hasn't moved in 10 days. What's the plan?

I had a call with @jozkee today. We have a mutual understanding now and will address the comments today.

… or when using conversion constructor.
Disallow dictionary with reserved keys when using the pax constructor that takes a dictionary.
Allow user to modify reserved fields via their properties and ensure they get updated in the dictionary upon writing entry to an archive.
@carlossanlop

Copy link
Copy Markdown
ContributorAuthor

All the apple queues are failing with an unrelated infra problem that is being investigated right now (thanks Stephen for notifying First Responders).

##[error]Git fetch failed with exit code: 128

{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
thrownewArgumentException(string.Format(SR.TarReservedExtendedAttribute,key));
thrownewArgumentException(SR.Format(SR.TarReservedExtendedAttribute,key));

Comment on lines +66 to 83
private string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
private long _size;
private DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
private string? _linkName;

// POSIX and GNU shared attributes

internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
private string? _gName;
private string? _uName;
internal int _devMajor;
internal int _devMinor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider grouping by and documenting the fields that are written/expected in the extended attributes.


_format = format;
_name = name;
Name = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name=name;
_name=name;

This can keep using the fields as all _isPaxEa* bools are false at this point. No? The same applies for the rest of the cases.

Comment on lines +195 to 206
if (!allowReservedKeys)
{
foreach ((string key, string _) in existing)
{
if (key is PaxEaName or PaxEaSize or PaxEaMTime or PaxEaGName or PaxEaUName)
{
throw new ArgumentException(string.Format(SR.TarReservedExtendedAttribute, key));
}
}
}

_ea = new Dictionary<string, string>(existing);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are now iterating existing twice when if (!allowReservedKeys). Can you please change it to iterate just once on that case?


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internalTarReader?_readerOfOrigin;
privateTarReader?_readerOfOrigin;

// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;

internal string Name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an entry and then change Name using the setter, will ExtendedAttributes["path"] return the updated value? Seems to me that it won't.

if (!_isPaxEaGNameSynced && !string.IsNullOrEmpty(GName))
{
TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName);
TryAddStringField(ExtendedAttributes, PaxEaGName, GName, FieldLengths.GName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryAddStringField will only change the dictionary element if Encoding.UTF8.GetByteCount(value) > maxLength and you are signaling _isPaxEaGNameSynced regardless of that condition.

if (!_isPaxEaUNameSynced && !string.IsNullOrEmpty(UName))
{
TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName);
TryAddStringField(ExtendedAttributes, PaxEaUName, UName, FieldLengths.UName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

if (_size > 99_999_999)
Size = GetTotalDataBytesToWrite();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were not doing this before, why is it needed now?

@jozkee

Copy link
Copy Markdown
Member

Closing in favor of #76404

@jozkeejozkee closed this Sep 29, 2022
@ghostghost locked as resolved and limited conversation to collaborators Oct 30, 2022
@carlossanlop
carlossanlop deleted the ExtendedAttributesCollect branch July 28, 2023 15:28
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tar: Not able to write a PaxTarEntry from a TarReader.

4 participants

@carlossanlop@stephentoub@jozkee@danmoseley