The hardest tarball?

Nov 7th, 2026

Here is a curiosity: an archive that can be constructed using a single GNU tar invocation, that is extracted in 8 distinct ways depending on what tool you use.

$ printf hello > file
$ touch empty
$ tar --format=pax --pax-option='delete=?time,size:=512' \
      --transform='s,^\./file$,é,' \
      -cf hardest.tar file ./file empty

It creates an archive hardest.tar from three files file ./file empty in sequence, where file is repeated so that GNU tar stores the second one as a hard link, and empty is empty. Exploiting the unique path of the second entry, the hard link is renamed to é. This triggers tar to create a pax header to store the file name as UTF-8. We add a size=512 record to the pax header, which overrides the default size=0 from the hard link. The value 512 is not a coincidence: it is exactly the size of a tar header.

Visually, this archive has the following structure, with two basic interpretations based on whether a hard link can have a size or not.

The structure of hardest.tar, with two basic interpretations based on whether a hard link can have a size or not.

Now, POSIX says that data blocks for a hard link “may be included” in pax archives, but the implications of this are not entirely clear. So the 512 bytes after the hard link are its data, and not the header of empty as we archived it? How can a hard link have different contents from its previous counterpart file?

Extracting it

This archive gives by far the richest and most surprising results I've encountered so far when extracting it with well-known tar utilities and libraries. The results below were produced using the latest releases of all packages at the time of writing.

GNU tar, BusyBox, Python, Go and Perl

This group is reasonable in principle: they ignore the size on the hard link, and extract file, its hard link é, and empty.

bsdtar

This is the system tar utility of macOS, which is closer to POSIX in spirit regarding the concept of size on hard links, and writes the 512 bytes into the hard link. The end result is that file now contains the tar header of empty.

7-Zip, node-tar, tar-fs, and Rust's tar & astral-tokio-tar

This group agrees with bsdtar that the 512 bytes are data of the hard link, but doesn't write them into the hard link. Instead, they skip forward over the data, and you are left with just file and é, without empty.

Java's Commons Compress

This is where you begin to need more imagination. Because é contains data, a separate file is created that contains the header of empty as data, while file keeps its original contents.

PHP's PharData

Makes é a separate, empty file, and also extracts empty. Why not?

RubyGems

Does not extract é at all, but to make up for it, it creates an empty directory PaxHeaders next to file and empty.

R

R has an entirely different approach: it extracts empty with 512 null bytes. It mistakenly applies the size to empty instead of the hard link, and those 512 bytes are from an end-of-archive marker.

Bonus: Haskell

Haskell's tar would be exactly like GNU tar, if only it did not have string decoding issues, which turn the file é into é on disk (in the year 2026, this is not a rendering quirk of your browser).

.NET, Julia and uv's tar-codec

These implementations reject the tarball. I believe this is the best thing you can do, even if it's technically not POSIX-compliant.

More of this

This tarball is one of 134 tricky tarballs that I'm collecting and categorizing in my test suite haampie/tar-minefield, developed since the last post. Its website https://stoppels.ch/tar features a big table that shows how implementations deal with all these cases, as well as detailed info per test case. A variant from the tarball of this post is included as a test case.

If you'd like to discuss this, send me an email at firstname[at]lastname.ch.