# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

package Dpkg::Source::Archive;

use strict;
use warnings;

our $VERSION = "0.01";

use Dpkg::Source::Functions qw(erasedir fixperms);
use Dpkg::Gettext;
use Dpkg::IPC;
use Dpkg::ErrorHandling;

use File::Temp qw(tempdir);
use File::Basename qw(basename);
use File::Spec;
use Cwd;

use base 'Dpkg::Compression::FileHandle';

sub create {
    my ($self, %opts) = @_;
    $opts{"options"} ||= [];
    my %spawn_opts;
    # Possibly run tar from another directory
    if ($opts{"chdir"}) {
        $spawn_opts{"chdir"} = $opts{"chdir"};
        *$self->{"chdir"} = $opts{"chdir"};
    }
    # Redirect input/output appropriately
    $self->ensure_open("w");
    $spawn_opts{"to_handle"} = $self->get_filehandle();
    $spawn_opts{"from_pipe"} = \*$self->{'tar_input'};
    # Call tar creation process
    $spawn_opts{"delete_env"} = [ "TAR_OPTIONS" ];
    $spawn_opts{'exec'} = [ 'tar', '--null', '-T', '-', '--numeric-owner',
                            '--owner', '0', '--group', '0',
                            @{$opts{"options"}}, '-cf', '-' ];
    *$self->{"pid"} = spawn(%spawn_opts);
    *$self->{"cwd"} = getcwd();
}

sub _add_entry {
    my ($self, $file) = @_;
    my $cwd = *$self->{'cwd'};
    internerr("call create() first") unless *$self->{"tar_input"};
    $file = $2 if ($file =~ /^\Q$cwd\E\/(.+)$/); # Relative names
    print({ *$self->{'tar_input'} } "$file\0") ||
	    syserr(_g("write on tar input"));
}

sub add_file {
    my ($self, $file) = @_;
    my $testfile = $file;
    if (*$self->{"chdir"}) {
        $testfile = File::Spec->catfile(*$self->{"chdir"}, $file);
    }
    internerr("add_file() doesn't handle directories") if not -l $testfile and -d _;
    $self->_add_entry($file);
}

sub add_directory {
    my ($self, $file) = @_;
    my $testfile = $file;
    if (*$self->{"chdir"}) {
        $testfile = File::Spec->catdir(*$self->{"chdir"}, $file);
    }
    internerr("add_directory() only handles directories") unless not -l $testfile and -d _;
    $self->_add_entry($file);
}

sub finish {
    my ($self) = @_;
    close(*$self->{'tar_input'}) or syserr(_g("close on tar input"));
    wait_child(*$self->{'pid'}, cmdline => 'tar -cf -');
    delete *$self->{'pid'};
    delete *$self->{'tar_input'};
    delete *$self->{'cwd'};
    delete *$self->{'chdir'};
    $self->close();
}

sub extract {
    my ($self, $dest, %opts) = @_;
    $opts{"options"} ||= [];
    $opts{"in_place"} ||= 0;
    $opts{"no_fixperms"} ||= 0;
    my %spawn_opts = (wait_child => 1);

    # Prepare destination
    my $tmp;
    if ($opts{"in_place"}) {
        $spawn_opts{"chdir"} = $dest;
        $tmp = $dest; # So that fixperms call works
    } else {
        my $template = basename($self->get_filename()) .  ".tmp-extract.XXXXX";
        unless (-e $dest) {
            # Kludge so that realpath works
            mkdir($dest) || syserr(_g("cannot create directory %s"), $dest);
        }
        $tmp = tempdir($template, DIR => Cwd::realpath("$dest/.."), CLEANUP => 1);
        $spawn_opts{"chdir"} = $tmp;
    }

    # Prepare stuff that handles the input of tar
    $self->ensure_open("r");
    $spawn_opts{"from_handle"} = $self->get_filehandle();

    # Call tar extraction process
    $spawn_opts{"delete_env"} = [ "TAR_OPTIONS" ];
    $spawn_opts{'exec'} = [ 'tar', '--no-same-owner', '--no-same-permissions',
                            @{$opts{"options"}}, '-xf', '-' ];
    spawn(%spawn_opts);
    $self->close();

    # Fix permissions on extracted files because tar insists on applying
    # our umask _to the original permissions_ rather than mostly-ignoring
    # the original permissions.
    # We still need --no-same-permissions because otherwise tar might
    # extract directory setgid (which we want inherited, not
    # extracted); we need --no-same-owner because putting the owner
    # back is tedious - in particular, correct group ownership would
    # have to be calculated using mount options and other madness.
    fixperms($tmp) unless $opts{"no_fixperms"};

    # Stop here if we extracted in-place as there's nothing to move around
    return if $opts{"in_place"};

    # Rename extracted directory
    opendir(D, $tmp) || syserr(_g("cannot opendir %s"), $tmp);
    my @entries = grep { $_ ne "." && $_ ne ".." } readdir(D);
    closedir(D);
    my $done = 0;
    erasedir($dest);
    if (scalar(@entries) == 1 && ! -l "$tmp/$entries[0]" && -d _) {
	rename("$tmp/$entries[0]", $dest) ||
		syserr(_g("Unable to rename %s to %s"),
		       "$tmp/$entries[0]", $dest);
    } else {
	rename($tmp, $dest) ||
		syserr(_g("Unable to rename %s to %s"), $tmp, $dest);
    }
    erasedir($tmp);
}

1;
