1# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- 2package CPAN::Tarzip; 3use strict; 4use vars qw($VERSION @ISA $BUGHUNTING); 5use CPAN::Debug; 6use File::Basename qw(basename); 7$VERSION = "5.5013"; 8# module is internal to CPAN.pm 9 10@ISA = qw(CPAN::Debug); ## no critic 11$BUGHUNTING ||= 0; # released code must have turned off 12 13# it's ok if file doesn't exist, it just matters if it is .gz or .bz2 14sub new { 15 my($class,$file) = @_; 16 $CPAN::Frontend->mydie("CPAN::Tarzip->new called without arg") unless defined $file; 17 my $me = { FILE => $file }; 18 if ($file =~ /\.(bz2|gz|zip|tbz|tgz)$/i) { 19 $me->{ISCOMPRESSED} = 1; 20 } else { 21 $me->{ISCOMPRESSED} = 0; 22 } 23 if (0) { 24 } elsif ($file =~ /\.(?:bz2|tbz)$/i) { 25 unless ($me->{UNGZIPPRG} = $CPAN::Config->{bzip2}) { 26 my $bzip2 = _my_which("bzip2"); 27 if ($bzip2) { 28 $me->{UNGZIPPRG} = $bzip2; 29 } else { 30 $CPAN::Frontend->mydie(qq{ 31CPAN.pm needs the external program bzip2 in order to handle '$file'. 32Please install it now and run 'o conf init bzip2' from the 33CPAN shell prompt to register it as external program. 34}); 35 } 36 } 37 } else { 38 $me->{UNGZIPPRG} = _my_which("gzip"); 39 } 40 $me->{TARPRG} = _my_which("tar") || _my_which("gtar"); 41 bless $me, $class; 42} 43 44sub _zlib_ok () { 45 $CPAN::META->has_inst("Compress::Zlib") or return; 46 Compress::Zlib->can('gzopen'); 47} 48 49sub _my_which { 50 my($what) = @_; 51 if ($CPAN::Config->{$what}) { 52 return $CPAN::Config->{$what}; 53 } 54 if ($CPAN::META->has_inst("File::Which")) { 55 return File::Which::which($what); 56 } 57 my @cand = MM->maybe_command($what); 58 return $cand[0] if @cand; 59 require File::Spec; 60 my $component; 61 PATH_COMPONENT: foreach $component (File::Spec->path()) { 62 next unless defined($component) && $component; 63 my($abs) = File::Spec->catfile($component,$what); 64 if (MM->maybe_command($abs)) { 65 return $abs; 66 } 67 } 68 return; 69} 70 71sub gzip { 72 my($self,$read) = @_; 73 my $write = $self->{FILE}; 74 if (_zlib_ok) { 75 my($buffer,$fhw); 76 $fhw = FileHandle->new($read) 77 or $CPAN::Frontend->mydie("Could not open $read: $!"); 78 my $cwd = `pwd`; 79 my $gz = Compress::Zlib::gzopen($write, "wb") 80 or $CPAN::Frontend->mydie("Cannot gzopen $write: $! (pwd is $cwd)\n"); 81 binmode($fhw); 82 $gz->gzwrite($buffer) 83 while read($fhw,$buffer,4096) > 0 ; 84 $gz->gzclose() ; 85 $fhw->close; 86 return 1; 87 } else { 88 my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); 89 system(qq{$command -c "$read" > "$write"})==0; 90 } 91} 92 93 94sub gunzip { 95 my($self,$write) = @_; 96 my $read = $self->{FILE}; 97 if (_zlib_ok) { 98 my($buffer,$fhw); 99 $fhw = FileHandle->new(">$write") 100 or $CPAN::Frontend->mydie("Could not open >$write: $!"); 101 my $gz = Compress::Zlib::gzopen($read, "rb") 102 or $CPAN::Frontend->mydie("Cannot gzopen $read: $!\n"); 103 binmode($fhw); 104 $fhw->print($buffer) 105 while $gz->gzread($buffer) > 0 ; 106 $CPAN::Frontend->mydie("Error reading from $read: $!\n") 107 if $gz->gzerror != Compress::Zlib::Z_STREAM_END(); 108 $gz->gzclose() ; 109 $fhw->close; 110 return 1; 111 } else { 112 my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); 113 system(qq{$command -d -c "$read" > "$write"})==0; 114 } 115} 116 117 118sub gtest { 119 my($self) = @_; 120 return $self->{GTEST} if exists $self->{GTEST}; 121 defined $self->{FILE} or $CPAN::Frontend->mydie("gtest called but no FILE specified"); 122 my $read = $self->{FILE}; 123 my $success; 124 if ($read=~/\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { 125 my($buffer,$len); 126 $len = 0; 127 my $gz = Compress::Bzip2::bzopen($read, "rb") 128 or $CPAN::Frontend->mydie(sprintf("Cannot bzopen %s: %s\n", 129 $read, 130 $Compress::Bzip2::bzerrno)); 131 while ($gz->bzread($buffer) > 0 ) { 132 $len += length($buffer); 133 $buffer = ""; 134 } 135 my $err = $gz->bzerror; 136 $success = ! $err || $err == Compress::Bzip2::BZ_STREAM_END(); 137 if ($len == -s $read) { 138 $success = 0; 139 CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; 140 } 141 $gz->gzclose(); 142 CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; 143 } elsif ( $read=~/\.(?:gz|tgz)$/ && _zlib_ok ) { 144 # After I had reread the documentation in zlib.h, I discovered that 145 # uncompressed files do not lead to an gzerror (anymore?). 146 my($buffer,$len); 147 $len = 0; 148 my $gz = Compress::Zlib::gzopen($read, "rb") 149 or $CPAN::Frontend->mydie(sprintf("Cannot gzopen %s: %s\n", 150 $read, 151 $Compress::Zlib::gzerrno)); 152 while ($gz->gzread($buffer) > 0 ) { 153 $len += length($buffer); 154 $buffer = ""; 155 } 156 my $err = $gz->gzerror; 157 $success = ! $err || $err == Compress::Zlib::Z_STREAM_END(); 158 if ($len == -s $read) { 159 $success = 0; 160 CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; 161 } 162 $gz->gzclose(); 163 CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; 164 } elsif (!$self->{ISCOMPRESSED}) { 165 $success = 0; 166 } else { 167 my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); 168 $success = 0==system(qq{$command -qdt "$read"}); 169 } 170 return $self->{GTEST} = $success; 171} 172 173 174sub TIEHANDLE { 175 my($class,$file) = @_; 176 my $ret; 177 $class->debug("file[$file]"); 178 my $self = $class->new($file); 179 if (0) { 180 } elsif (!$self->gtest) { 181 my $fh = FileHandle->new($file) 182 or $CPAN::Frontend->mydie("Could not open file[$file]: $!"); 183 binmode $fh; 184 $self->{FH} = $fh; 185 $class->debug("via uncompressed FH"); 186 } elsif ($file =~ /\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { 187 my $gz = Compress::Bzip2::bzopen($file,"rb") or 188 $CPAN::Frontend->mydie("Could not bzopen $file"); 189 $self->{GZ} = $gz; 190 $class->debug("via Compress::Bzip2"); 191 } elsif ($file =~/\.(?:gz|tgz)$/ && _zlib_ok) { 192 my $gz = Compress::Zlib::gzopen($file,"rb") or 193 $CPAN::Frontend->mydie("Could not gzopen $file"); 194 $self->{GZ} = $gz; 195 $class->debug("via Compress::Zlib"); 196 } else { 197 my $gzip = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); 198 my $pipe = "$gzip -d -c $file |"; 199 my $fh = FileHandle->new($pipe) or $CPAN::Frontend->mydie("Could not pipe[$pipe]: $!"); 200 binmode $fh; 201 $self->{FH} = $fh; 202 $class->debug("via external $gzip"); 203 } 204 $self; 205} 206 207 208sub READLINE { 209 my($self) = @_; 210 if (exists $self->{GZ}) { 211 my $gz = $self->{GZ}; 212 my($line,$bytesread); 213 $bytesread = $gz->gzreadline($line); 214 return undef if $bytesread <= 0; 215 return $line; 216 } else { 217 my $fh = $self->{FH}; 218 return scalar <$fh>; 219 } 220} 221 222 223sub READ { 224 my($self,$ref,$length,$offset) = @_; 225 $CPAN::Frontend->mydie("read with offset not implemented") if defined $offset; 226 if (exists $self->{GZ}) { 227 my $gz = $self->{GZ}; 228 my $byteread = $gz->gzread($$ref,$length);# 30eaf79e8b446ef52464b5422da328a8 229 return $byteread; 230 } else { 231 my $fh = $self->{FH}; 232 return read($fh,$$ref,$length); 233 } 234} 235 236 237sub DESTROY { 238 my($self) = @_; 239 if (exists $self->{GZ}) { 240 my $gz = $self->{GZ}; 241 $gz->gzclose() if defined $gz; # hard to say if it is allowed 242 # to be undef ever. AK, 2000-09 243 } else { 244 my $fh = $self->{FH}; 245 $fh->close if defined $fh; 246 } 247 undef $self; 248} 249 250sub untar { 251 my($self) = @_; 252 my $file = $self->{FILE}; 253 my($prefer) = 0; 254 255 my $exttar = $self->{TARPRG} || ""; 256 $exttar = "" if $exttar =~ /^\s+$/; # user refuses to use it 257 my $extgzip = $self->{UNGZIPPRG} || ""; 258 $extgzip = "" if $extgzip =~ /^\s+$/; # user refuses to use it 259 260 if (0) { # makes changing order easier 261 } elsif ($BUGHUNTING) { 262 $prefer=2; 263 } elsif ($CPAN::Config->{prefer_external_tar}) { 264 $prefer = 1; 265 } elsif ( 266 $CPAN::META->has_usable("Archive::Tar") 267 && 268 _zlib_ok ) { 269 my $prefer_external_tar = $CPAN::Config->{prefer_external_tar}; 270 unless (defined $prefer_external_tar) { 271 if ($^O =~ /(MSWin32|solaris)/) { 272 $prefer_external_tar = 0; 273 } else { 274 $prefer_external_tar = 1; 275 } 276 } 277 $prefer = $prefer_external_tar ? 1 : 2; 278 } elsif ($exttar && $extgzip) { 279 # no modules and not bz2 280 $prefer = 1; 281 # but solaris binary tar is a problem 282 if ($^O eq 'solaris' && qx($exttar --version 2>/dev/null) !~ /gnu/i) { 283 $CPAN::Frontend->mywarn(<< 'END_WARN'); 284 285WARNING: Many CPAN distributions were archived with GNU tar and some of 286them may be incompatible with Solaris tar. We respectfully suggest you 287configure CPAN to use a GNU tar instead ("o conf init tar") or install 288a recent Archive::Tar instead; 289 290END_WARN 291 } 292 } else { 293 my $foundtar = $exttar ? "'$exttar'" : "nothing"; 294 my $foundzip = $extgzip ? "'$extgzip'" : $foundtar ? "nothing" : "also nothing"; 295 my $foundAT; 296 if ($CPAN::META->has_usable("Archive::Tar")) { 297 $foundAT = sprintf "'%s'", "Archive::Tar::"->VERSION; 298 } else { 299 $foundAT = "nothing"; 300 } 301 my $foundCZ; 302 if (_zlib_ok) { 303 $foundCZ = sprintf "'%s'", "Compress::Zlib::"->VERSION; 304 } elsif ($foundAT) { 305 $foundCZ = "nothing"; 306 } else { 307 $foundCZ = "also nothing"; 308 } 309 $CPAN::Frontend->mydie(qq{ 310 311CPAN.pm needs either the external programs tar and gzip -or- both 312modules Archive::Tar and Compress::Zlib installed. 313 314For tar I found $foundtar, for gzip $foundzip. 315 316For Archive::Tar I found $foundAT, for Compress::Zlib $foundCZ; 317 318Can't continue cutting file '$file'. 319}); 320 } 321 my $tar_verb = "v"; 322 if (defined $CPAN::Config->{tar_verbosity}) { 323 $tar_verb = $CPAN::Config->{tar_verbosity} eq "none" ? "" : 324 $CPAN::Config->{tar_verbosity}; 325 } 326 if ($prefer==1) { # 1 => external gzip+tar 327 my($system); 328 my $is_compressed = $self->gtest(); 329 my $tarcommand = CPAN::HandleConfig->safe_quote($exttar); 330 if ($is_compressed) { 331 my $command = CPAN::HandleConfig->safe_quote($extgzip); 332 $system = qq{$command -d -c }. 333 qq{< "$file" | $tarcommand x${tar_verb}f -}; 334 } else { 335 $system = qq{$tarcommand x${tar_verb}f "$file"}; 336 } 337 if (system($system) != 0) { 338 # people find the most curious tar binaries that cannot handle 339 # pipes 340 if ($is_compressed) { 341 (my $ungzf = $file) =~ s/\.gz(?!\n)\Z//; 342 $ungzf = basename $ungzf; 343 my $ct = CPAN::Tarzip->new($file); 344 if ($ct->gunzip($ungzf)) { 345 $CPAN::Frontend->myprint(qq{Uncompressed $file successfully\n}); 346 } else { 347 $CPAN::Frontend->mydie(qq{Couldn\'t uncompress $file\n}); 348 } 349 $file = $ungzf; 350 } 351 $system = qq{$tarcommand x${tar_verb}f "$file"}; 352 $CPAN::Frontend->myprint(qq{Using Tar:$system:\n}); 353 my $ret = system($system); 354 if ($ret==0) { 355 $CPAN::Frontend->myprint(qq{Untarred $file successfully\n}); 356 } else { 357 if ($? == -1) { 358 $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: '%s'\n}, 359 $file, $!); 360 } elsif ($? & 127) { 361 $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: child died with signal %d, %s coredump\n}, 362 $file, ($? & 127), ($? & 128) ? 'with' : 'without'); 363 } else { 364 $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: child exited with value %d\n}, 365 $file, $? >> 8); 366 } 367 } 368 return 1; 369 } else { 370 return 1; 371 } 372 } elsif ($prefer==2) { # 2 => modules 373 unless ($CPAN::META->has_usable("Archive::Tar")) { 374 $CPAN::Frontend->mydie("Archive::Tar not installed, please install it to continue"); 375 } 376 # Make sure AT does not use uid/gid/permissions in the archive 377 # This leaves it to the user's umask instead 378 local $Archive::Tar::CHMOD = 1; 379 local $Archive::Tar::SAME_PERMISSIONS = 0; 380 # Make sure AT leaves current user as owner 381 local $Archive::Tar::CHOWN = 0; 382 my $tar = Archive::Tar->new($file,1); 383 my $af; # archive file 384 my @af; 385 if ($BUGHUNTING) { 386 # RCS 1.337 had this code, it turned out unacceptable slow but 387 # it revealed a bug in Archive::Tar. Code is only here to hunt 388 # the bug again. It should never be enabled in published code. 389 # GDGraph3d-0.53 was an interesting case according to Larry 390 # Virden. 391 warn(">>>Bughunting code enabled<<< " x 20); 392 for $af ($tar->list_files) { 393 if ($af =~ m!^(/|\.\./)!) { 394 $CPAN::Frontend->mydie("ALERT: Archive contains ". 395 "illegal member [$af]"); 396 } 397 $CPAN::Frontend->myprint("$af\n"); 398 $tar->extract($af); # slow but effective for finding the bug 399 return if $CPAN::Signal; 400 } 401 } else { 402 for $af ($tar->list_files) { 403 if ($af =~ m!^(/|\.\./)!) { 404 $CPAN::Frontend->mydie("ALERT: Archive contains ". 405 "illegal member [$af]"); 406 } 407 if ($tar_verb eq "v" || $tar_verb eq "vv") { 408 $CPAN::Frontend->myprint("$af\n"); 409 } 410 push @af, $af; 411 return if $CPAN::Signal; 412 } 413 $tar->extract(@af) or 414 $CPAN::Frontend->mydie("Could not untar with Archive::Tar."); 415 } 416 417 Mac::BuildTools::convert_files([$tar->list_files], 1) 418 if ($^O eq 'MacOS'); 419 420 return 1; 421 } 422} 423 424sub unzip { 425 my($self) = @_; 426 my $file = $self->{FILE}; 427 if ($CPAN::META->has_inst("Archive::Zip")) { 428 # blueprint of the code from Archive::Zip::Tree::extractTree(); 429 my $zip = Archive::Zip->new(); 430 my $status; 431 $status = $zip->read($file); 432 $CPAN::Frontend->mydie("Read of file[$file] failed\n") 433 if $status != Archive::Zip::AZ_OK(); 434 $CPAN::META->debug("Successfully read file[$file]") if $CPAN::DEBUG; 435 my @members = $zip->members(); 436 for my $member ( @members ) { 437 my $af = $member->fileName(); 438 if ($af =~ m!^(/|\.\./)!) { 439 $CPAN::Frontend->mydie("ALERT: Archive contains ". 440 "illegal member [$af]"); 441 } 442 $status = $member->extractToFileNamed( $af ); 443 $CPAN::META->debug("af[$af]status[$status]") if $CPAN::DEBUG; 444 $CPAN::Frontend->mydie("Extracting of file[$af] from zipfile[$file] failed\n") if 445 $status != Archive::Zip::AZ_OK(); 446 return if $CPAN::Signal; 447 } 448 return 1; 449 } elsif ( my $unzip = $CPAN::Config->{unzip} ) { 450 my @system = ($unzip, $file); 451 return system(@system) == 0; 452 } 453 else { 454 $CPAN::Frontend->mydie(<<"END"); 455 456Can't unzip '$file': 457 458You have not configured an 'unzip' program and do not have Archive::Zip 459installed. Please either install Archive::Zip or else configure 'unzip' 460by running the command 'o conf init unzip' from the CPAN shell prompt. 461 462END 463 } 464} 465 4661; 467 468__END__ 469 470=head1 NAME 471 472CPAN::Tarzip - internal handling of tar archives for CPAN.pm 473 474=head1 LICENSE 475 476This program is free software; you can redistribute it and/or 477modify it under the same terms as Perl itself. 478 479=cut 480