The broken error handling of Archive::Tar

This took me a long session to find out, so I'll document it here: Archive::Tar has no (usable) error handling.

What I needed to do was to catch broken tar files where some files did not extract. The default behaviour is to output a warning to stdout and proceed with the next file as if nothing had happened.


The first thing that sticks out is $Archive::Tar::WARN: the module spews error messages to STDERR which is inappropriate for a library. If the program is run from cron there will be an email for every warning (you do read your server mail, do you?!). Also I want to handle the error, and not just be informed about it. On the other hand, the documentation says
    Set this variable to 0 if you do not want any warnings printed. Personally I recommend against doing this,
    but people asked for the option. Also, be advised that this is of course not threadsafe.

    Defaults to 1.
So why is it not recommended? The answer seems to be: because there is no other possibility to handle certain errors! It's either a warning or nothing!



The code reference returned by iter simply jumps over files that are not extractable, giving no indication to the caller that anything bad has happened!

$tar->error() and $Archive::Tar::error

These two should be identical, but $tar->error() never returned anything for me.

The ugly workaround

The best idea I had was to use $Archive::Tar::error as an error indicator by comparing it to an empty string. This value is the default – everything else must be an error. Care must be taken to always reset the variable, because it is global to the module. This is not thread safe.
sub tar_error_reset
    $Archive::Tar::error = "";

sub tar_error
    return $Archive::Tar::error ne "";

sub tar_errormsg
    return $Archive::Tar::error;

# Returns files extracted
# Returns if an error is encountered.
# The caller must use tar_error() to check for success!
# The error message is available via tar_errormsg()
# The files extracted so far can be deleted.
sub tar_extract_all
    my $tarfile = shift;

    my $tar = new Archive::Tar;

    my $next = $tar->iter($tarfile);
    return () if (tar_error());

    my @extracted;
    while (my $tarfile_object = $next->()) {
        $tarfile_object->extract() or last;
        last if (!defined $tarfile_object);
        push @extracted, $tarfile_object;
        last if (tar_error());

    return @extracted;