| # 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 POSIX; |
| 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; |