1# ex:ts=8 sw=4: 2# $OpenBSD: Ustar.pm,v 1.89 2021/08/09 16:40:20 espie Exp $ 3# 4# Copyright (c) 2002-2014 Marc Espie <espie@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18# Handle utar archives 19 20use strict; 21use warnings; 22 23package OpenBSD::Ustar; 24 25use constant { 26 FILE => "\0", 27 FILE1 => '0', 28 HARDLINK => '1', 29 SOFTLINK => '2', 30 CHARDEVICE => '3', 31 BLOCKDEVICE => '4', 32 DIR => '5', 33 FIFO => '6', 34 CONTFILE => '7', 35 USTAR_HEADER => 'a100a8a8a8a12a12a8aa100a6a2a32a32a8a8a155a12', 36 MAXFILENAME => 100, 37 MAXLINKNAME => 100, 38 MAXPREFIX => 155, 39 MAXUSERNAME => 32, 40 MAXGROUPNAME => 32, 41 XHDR => 'x', 42 # XXX those are NOT supported, just recognized 43 GHDR => 'g', 44 LONGLINK => 'K', 45 LONGNAME => 'L', 46}; 47 48use File::Basename (); 49use OpenBSD::IdCache; 50use OpenBSD::Paths; 51 52our $uidcache = new OpenBSD::UidCache; 53our $gidcache = new OpenBSD::GidCache; 54our $unamecache = new OpenBSD::UnameCache; 55our $gnamecache = new OpenBSD::GnameCache; 56 57# This is a multiple of st_blksize everywhere.... 58my $buffsize = 2 * 1024 * 1024; 59 60sub new 61{ 62 my ($class, $fh, $state, $destdir) = @_; 63 64 $destdir = '' unless defined $destdir; 65 66 return bless { 67 fh => $fh, 68 swallow => 0, 69 state => $state, 70 key => {}, 71 destdir => $destdir} , $class; 72} 73 74sub set_description 75{ 76 my ($self, $d) = @_; 77 $self->{description} = $d; 78} 79 80sub set_callback 81{ 82 my ($self, $code) = @_; 83 $self->{callback} = $code; 84} 85 86sub fatal 87{ 88 my ($self, $msg, @args) = @_; 89 $self->{state}->fatal("Ustar [#1][#2]: #3", 90 $self->{description} // '?', $self->{lastname} // '?', 91 $self->{state}->f($msg, @args)); 92} 93 94sub new_object 95{ 96 my ($self, $h, $class) = @_; 97 $h->{archive} = $self; 98 $h->{destdir} = $self->{destdir}; 99 bless $h, $class; 100 return $h; 101} 102 103sub skip 104{ 105 my $self = shift; 106 my $temp; 107 108 while ($self->{swallow} > 0) { 109 my $toread = $self->{swallow}; 110 if ($toread >$buffsize) { 111 $toread = $buffsize; 112 } 113 my $actual = read($self->{fh}, $temp, $toread); 114 if (!defined $actual) { 115 $self->fatal("Error while skipping archive: #1", $!); 116 } 117 if ($actual == 0) { 118 $self->fatal("Premature end of archive in header: #1", $!); 119 } 120 $self->{swallow} -= $actual; 121 } 122} 123 124my $types = { 125 DIR , 'OpenBSD::Ustar::Dir', 126 HARDLINK , 'OpenBSD::Ustar::HardLink', 127 SOFTLINK , 'OpenBSD::Ustar::SoftLink', 128 FILE , 'OpenBSD::Ustar::File', 129 FILE1 , 'OpenBSD::Ustar::File', 130 FIFO , 'OpenBSD::Ustar::Fifo', 131 CHARDEVICE , 'OpenBSD::Ustar::CharDevice', 132 BLOCKDEVICE , 'OpenBSD::Ustar::BlockDevice', 133}; 134 135my $unsupported = { 136 XHDR => 'Extended header', 137 GHDR => 'GNU header', 138 LONGLINK => 'Long symlink', 139 LONGNAME => 'Long file', 140}; 141 142sub read_records 143{ 144 my ($self, $size) = @_; 145 my $toread = $self->{swallow}; 146 my $result = ''; 147 while ($toread > 0) { 148 my $buffer; 149 my $maxread = $buffsize; 150 $maxread = $toread if $maxread > $toread; 151 my $actual = read($self->{fh}, $buffer, $maxread); 152 if (!defined $actual) { 153 $self->fatal("Error reading from archive: #1", $!); 154 } 155 if ($actual == 0) { 156 $self->fatal("Premature end of archive"); 157 } 158 $self->{swallow} -= $actual; 159 $toread -= $actual; 160 $result .= $buffer; 161 } 162 return substr($result, 0, $size); 163} 164 165sub parse_records 166{ 167 my ($self, $result, $h) = @_; 168 open(my $fh, '<', \$h); 169 while (<$fh>) { 170 chomp; 171 if (m/^(\d+)\s+(\w+?)\=(.*)$/) { 172 my ($k, $v) = ($2, $3); 173 if ($k eq 'path') { 174 $result->{name} = $v; 175 } elsif ($k eq 'linkpath') { 176 $result->{linkname} = $v; 177 } 178 } 179 } 180} 181 182sub next 183{ 184 my $self = shift; 185 # get rid of the current object 186 $self->skip; 187 my $header; 188 my $n = read($self->{fh}, $header, 512); 189 return if (defined $n) and $n == 0; 190 $self->fatal("Error while reading header") 191 unless defined $n and $n == 512; 192 if ($header eq "\0"x512) { 193 return $self->next; 194 } 195 # decode header 196 my ($name, $mode, $uid, $gid, $size, $mtime, $chksum, $type, 197 $linkname, $magic, $version, $uname, $gname, $major, $minor, 198 $prefix, $pad) = unpack(USTAR_HEADER, $header); 199 if ($magic ne "ustar\0" || $version ne '00') { 200 $self->fatal("Not an ustar archive header"); 201 } 202 # verify checksum 203 my $value = $header; 204 substr($value, 148, 8) = " "x8; 205 my $ck2 = unpack("%C*", $value); 206 if ($ck2 != oct($chksum)) { 207 $self->fatal("Bad archive checksum"); 208 } 209 $name =~ s/\0*$//o; 210 $mode = oct($mode) & 0xfff; 211 $uname =~ s/\0*$//o; 212 $gname =~ s/\0*$//o; 213 $linkname =~ s/\0*$//o; 214 $major = oct($major); 215 $minor = oct($minor); 216 $uid = oct($uid); 217 $gid = oct($gid); 218 $uid = $uidcache->lookup($uname, $uid); 219 $gid = $gidcache->lookup($gname, $gid); 220 { 221 no warnings; # XXX perl warns if oct converts >= 2^32 values 222 $mtime = oct($mtime); 223 } 224 unless ($prefix =~ m/^\0/o) { 225 $prefix =~ s/\0*$//o; 226 $name = "$prefix/$name"; 227 } 228 229 $self->{lastname} = $name; 230 $size = oct($size); 231 my $result= { 232 name => $name, 233 mode => $mode, 234 atime => $mtime, 235 mtime => $mtime, 236 linkname=> $linkname, 237 uname => $uname, 238 uid => $uid, 239 gname => $gname, 240 gid => $gid, 241 size => $size, 242 major => $major, 243 minor => $minor, 244 }; 245 # adjust swallow 246 $self->{swallow} = $size; 247 if ($size % 512) { 248 $self->{swallow} += 512 - $size % 512; 249 } 250 if ($type eq XHDR) { 251 my $h = $self->read_records($size); 252 $result = $self->next; 253 $self->parse_records($result, $h); 254 return $result; 255 } 256 if (defined $types->{$type}) { 257 $self->new_object($result, $types->{$type}); 258 } else { 259 $self->fatal("Unsupported type #1 (#2)", $type, 260 $unsupported->{$type} // "unknown"); 261 } 262 if (!$result->isFile && $result->{size} != 0) { 263 $self->fatal("Bad archive: non null size for #1 (#2)", 264 $types->{$type}, $result->{name}); 265 } 266 267 $self->{cachename} = $name; 268 return $result; 269} 270 271sub split_name 272{ 273 my $name = shift; 274 my $prefix = ''; 275 276 my $l = length $name; 277 if ($l > MAXFILENAME && $l <= MAXFILENAME+MAXPREFIX+1) { 278 while (length($name) > MAXFILENAME && 279 $name =~ m/^(.*?\/)(.*)$/o) { 280 $prefix .= $1; 281 $name = $2; 282 } 283 $prefix =~ s|/$||; 284 } 285 return ($prefix, $name); 286} 287 288sub extended_record 289{ 290 my ($k, $v) = @_; 291 my $string = " $k=$v\n"; 292 my $len = length($string); 293 if ($len < 995) { 294 return sprintf("%3d", $len+3).$string; 295 } elsif ($len < 9995) { 296 return sprintf("%04d", $len+4).$string; 297 } else { 298 return sprintf("%05d", $len+5).$string; 299 } 300} 301 302sub pack_header 303{ 304 my ($archive, $type, $size, $entry, $prefix, $name, $linkname, 305 $uname, $gname, $major, $minor) = @_; 306 307 my $header; 308 my $cksum = ' 'x8; 309 for (1 .. 2) { 310 $header = pack(USTAR_HEADER, 311 $name, 312 sprintf("%07o", $entry->{mode}), 313 sprintf("%07o", $entry->{uid} // 0), 314 sprintf("%07o", $entry->{gid} // 0), 315 sprintf("%011o", $size), 316 sprintf("%011o", $entry->{mtime} // 0), 317 $cksum, 318 $type, 319 $linkname, 320 'ustar', '00', 321 $uname, 322 $gname, 323 sprintf("%07o", $major), 324 sprintf("%07o", $minor), 325 $prefix, "\0"); 326 $cksum = sprintf("%07o", unpack("%C*", $header)); 327 } 328 return $header; 329} 330 331my $whatever = "usualSuspect000"; 332 333sub mkheader 334{ 335 my ($archive, $entry, $type) = @_; 336 my ($prefix, $name) = split_name($entry->name); 337 my ($extendedname, $extendedlink); 338 my $linkname = $entry->{linkname}; 339 my $size = $entry->{size}; 340 my ($major, $minor); 341 if ($entry->isDevice) { 342 $major = $entry->{major}; 343 $minor = $entry->{minor}; 344 } else { 345 $major = 0; 346 $minor = 0; 347 } 348 my ($uname, $gname); 349 if (defined $entry->{uname}) { 350 $uname = $entry->{uname}; 351 } else { 352 $uname = $entry->{uid}; 353 } 354 if (defined $entry->{gname}) { 355 $gname = $entry->{gname}; 356 } else { 357 $gname = $entry->{gid}; 358 } 359 360 if (defined $entry->{cwd}) { 361 my $cwd = $entry->{cwd}; 362 $cwd.='/' unless $cwd =~ m/\/$/o; 363 $linkname =~ s/^\Q$cwd\E//; 364 } 365 if (!defined $linkname) { 366 $linkname = ''; 367 } 368 if (length $prefix > MAXPREFIX) { 369 $prefix = substr($prefix, 0, MAXPREFIX); 370 $extendedname = 1; 371 } 372 if (length $name > MAXFILENAME) { 373 $name = substr($name, 0, MAXPREFIX); 374 $extendedname = 1; 375 } 376 if (length $linkname > MAXLINKNAME) { 377 $linkname = substr($linkname, 0, MAXLINKNAME); 378 $extendedlink = 1; 379 } 380 if (length $uname > MAXUSERNAME) { 381 $archive->fatal("Username too long #1", $uname); 382 } 383 if (length $gname > MAXGROUPNAME) { 384 $archive->fatal("Groupname too long #1", $gname); 385 } 386 my $header = $archive->pack_header($type, $size, $entry, 387 $prefix, $name, $linkname, $uname, $gname, $major, $minor); 388 my $x; 389 if ($extendedname) { 390 $x .= extended_record("path", $entry->name); 391 } 392 if ($extendedlink) { 393 $x .= extended_record("linkpath",$entry->{linkname}); 394 } 395 if ($x) { 396 my $extended = $archive->pack_header(XHDR, length($x), $entry, 397 '', $whatever, '', $uname, $gname, $major, $minor); 398 $whatever++; 399 if ((length $x) % 512) { 400 $x .= "\0" x (512 - ((length $x) % 512)); 401 } 402 return $extended.$x.$header; 403 } 404 return $header; 405} 406 407sub prepare 408{ 409 my ($self, $filename, $destdir) = @_; 410 411 $destdir //= $self->{destdir}; 412 my $realname = "$destdir/$filename"; 413 414 my ($dev, $ino, $mode, $uid, $gid, $rdev, $size, $mtime) = 415 (lstat $realname)[0,1,2, 4,5,6,7, 9]; 416 417 my $entry = { 418 key => "$dev/$ino", 419 name => $filename, 420 realname => $realname, 421 mode => $mode, 422 uid => $uid, 423 gid => $gid, 424 size => $size, 425 mtime => $mtime, 426 uname => $unamecache->lookup($uid), 427 gname => $gnamecache->lookup($gid), 428 major => $rdev/256, 429 minor => $rdev%256, 430 }; 431 my $k = $entry->{key}; 432 my $class = "OpenBSD::Ustar::File"; # default 433 if (defined $self->{key}{$k}) { 434 $entry->{linkname} = $self->{key}{$k}; 435 $class = "OpenBSD::Ustar::HardLink"; 436 } elsif (-l $realname) { 437 $entry->{linkname} = readlink($realname); 438 $class = "OpenBSD::Ustar::SoftLink"; 439 } elsif (-p _) { 440 $class = "OpenBSD::Ustar::Fifo"; 441 } elsif (-c _) { 442 $class = "OpenBSD::Ustar::CharDevice"; 443 } elsif (-b _) { 444 $class ="OpenBSD::Ustar::BlockDevice"; 445 } elsif (-d _) { 446 $class = "OpenBSD::Ustar::Dir"; 447 } 448 $self->new_object($entry, $class); 449 if (!$entry->isFile) { 450 $entry->{size} = 0; 451 } 452 return $entry; 453} 454 455sub pad 456{ 457 my $self = shift; 458 my $fh = $self->{fh}; 459 print $fh "\0"x1024 or $self->fatal("Error writing to archive: #1", $!); 460} 461 462sub close 463{ 464 my $self = shift; 465 if (defined $self->{padout}) { 466 $self->pad; 467 } 468 close($self->{fh}); 469} 470 471sub destdir 472{ 473 my $self = shift; 474 if (@_ > 0) { 475 $self->{destdir} = shift; 476 } else { 477 return $self->{destdir}; 478 } 479} 480 481sub fh 482{ 483 return $_[0]->{fh}; 484} 485 486package OpenBSD::Ustar::Object; 487 488sub recheck_owner 489{ 490 my $entry = shift; 491 # XXX weird format to prevent cvs from expanding OpenBSD id 492 $entry->{uid} //= $OpenBSD::Ustar::uidcache 493 ->lookup($entry->{uname}); 494 $entry->{gid} //= $OpenBSD::Ustar::gidcache 495 ->lookup($entry->{gname}); 496} 497 498sub fatal 499{ 500 my ($self, @args) = @_; 501 $self->{archive}->fatal(@args); 502} 503 504sub system 505{ 506 my ($self, @args) = @_; 507 $self->{archive}{state}->system(@args); 508} 509 510sub errsay 511{ 512 my ($self, @args) = @_; 513 $self->{archive}{state}->errsay(@args); 514} 515sub left_todo 516{ 517 my ($self, $toread) = @_; 518 return if $toread == 0; 519 return unless defined $self->{archive}{callback}; 520 &{$self->{archive}{callback}}($self->{size} - $toread); 521} 522 523sub name 524{ 525 my $self = shift; 526 return $self->{name}; 527} 528 529sub fullname 530{ 531 my $self = shift; 532 return $self->{destdir}.$self->{name}; 533} 534 535sub set_name 536{ 537 my ($self, $v) = @_; 538 $self->{name} = $v; 539} 540 541sub set_modes_on_object 542{ 543 my ($self, $o) = @_; 544 chown $self->{uid}, $self->{gid}, $o; 545 chmod $self->{mode}, $o; 546 if (defined $self->{mtime} || defined $self->{atime}) { 547 utime $self->{atime} // time, $self->{mtime} // time, $o; 548 } 549} 550 551sub set_modes 552{ 553 my $self = shift; 554 $self->set_modes_on_object($self->fullname); 555} 556 557sub ensure_dir 558{ 559 my ($self, $dir) = @_; 560 return if -d $dir; 561 $self->ensure_dir(File::Basename::dirname($dir)); 562 if (mkdir($dir)) { 563 return; 564 } 565 $self->fatal("Error making directory #1: #2", $dir, $!); 566} 567 568sub make_basedir 569{ 570 my $self = shift; 571 my $dir = $self->{destdir}.File::Basename::dirname($self->name); 572 $self->ensure_dir($dir); 573} 574 575sub write 576{ 577 my $self = shift; 578 my $arc = $self->{archive}; 579 my $out = $arc->{fh}; 580 581 $arc->{padout} = 1; 582 my $header = $arc->mkheader($self, $self->type); 583 print $out $header or $self->fatal("Error writing to archive: #1", $!); 584 $self->write_contents($arc); 585 my $k = $self->{key}; 586 if (!defined $arc->{key}{$k}) { 587 $arc->{key}{$k} = $self->name; 588 } 589} 590 591sub alias 592{ 593 my ($self, $arc, $alias) = @_; 594 595 my $k = $self->{archive}.":".$self->{archive}{cachename}; 596 if (!defined $arc->{key}{$k}) { 597 $arc->{key}{$k} = $alias; 598 } 599} 600 601sub write_contents 602{ 603 # only files have anything to write 604} 605 606sub resolve_links 607{ 608 # only hard links must cheat 609} 610 611sub copy_contents 612{ 613 # only files need copying 614} 615 616sub copy 617{ 618 my ($self, $wrarc) = @_; 619 my $out = $wrarc->{fh}; 620 $self->resolve_links($wrarc); 621 $wrarc->{padout} = 1; 622 my $header = $wrarc->mkheader($self, $self->type); 623 print $out $header or $self->fatal("Error writing to archive: #1", $!); 624 625 $self->copy_contents($wrarc); 626} 627 628sub isDir() { 0 } 629sub isFile() { 0 } 630sub isDevice() { 0 } 631sub isFifo() { 0 } 632sub isLink() { 0 } 633sub isSymLink() { 0 } 634sub isHardLink() { 0 } 635 636package OpenBSD::Ustar::Dir; 637our @ISA=qw(OpenBSD::Ustar::Object); 638 639sub create 640{ 641 my $self = shift; 642 $self->ensure_dir($self->fullname); 643 $self->set_modes; 644} 645 646sub isDir() { 1 } 647 648sub type() { OpenBSD::Ustar::DIR } 649 650package OpenBSD::Ustar::HardLink; 651our @ISA=qw(OpenBSD::Ustar::Object); 652 653sub create 654{ 655 my $self = shift; 656 $self->make_basedir; 657 my $linkname = $self->{linkname}; 658 if (defined $self->{cwd}) { 659 $linkname=$self->{cwd}.'/'.$linkname; 660 } 661 link $self->{destdir}.$linkname, $self->fullname or 662 $self->fatal("Can't link #1#2 to #1#3: #4", 663 $self->{destdir}, $linkname, $self->name, $!); 664} 665 666sub resolve_links 667{ 668 my ($self, $arc) = @_; 669 670 my $k = $self->{archive}.":".$self->{linkname}; 671 if (defined $arc->{key}{$k}) { 672 $self->{linkname} = $arc->{key}{$k}; 673 } else { 674 print join("\n", keys(%{$arc->{key}})), "\n"; 675 $self->fatal("Can't copy link over: original for #1 NOT available", $k); 676 } 677} 678 679sub isLink() { 1 } 680sub isHardLink() { 1 } 681 682sub type() { OpenBSD::Ustar::HARDLINK } 683 684package OpenBSD::Ustar::SoftLink; 685our @ISA=qw(OpenBSD::Ustar::Object); 686 687sub create 688{ 689 my $self = shift; 690 $self->make_basedir; 691 symlink $self->{linkname}, $self->fullname or 692 $self->fatal("Can't symlink #1 to #2: #3", 693 $self->{linkname}, $self->fullname, $!); 694 require POSIX; 695 POSIX::lchown($self->{uid}, $self->{gid}, $self->fullname); 696} 697 698sub isLink() { 1 } 699sub isSymLink() { 1 } 700 701sub type() { OpenBSD::Ustar::SOFTLINK } 702 703package OpenBSD::Ustar::Fifo; 704our @ISA=qw(OpenBSD::Ustar::Object); 705 706sub create 707{ 708 my $self = shift; 709 $self->make_basedir; 710 require POSIX; 711 POSIX::mkfifo($self->fullname, $self->{mode}) or 712 $self->fatal("Can't create fifo #1: #2", $self->fullname, $!); 713 $self->set_modes; 714} 715 716sub isFifo() { 1 } 717sub type() { OpenBSD::Ustar::FIFO } 718 719package OpenBSD::UStar::Device; 720our @ISA=qw(OpenBSD::Ustar::Object); 721 722sub create 723{ 724 my $self = shift; 725 $self->make_basedir; 726 $self->system(OpenBSD::Paths->mknod, 727 '-m', $self->{mode}, '--', $self->fullname, 728 $self->devicetype, $self->{major}, $self->{minor}); 729 $self->set_modes; 730} 731 732sub isDevice() { 1 } 733 734package OpenBSD::Ustar::BlockDevice; 735our @ISA=qw(OpenBSD::Ustar::Device); 736 737sub type() { OpenBSD::Ustar::BLOCKDEVICE } 738sub devicetype() { 'b' } 739 740package OpenBSD::Ustar::CharDevice; 741our @ISA=qw(OpenBSD::Ustar::Device); 742 743sub type() { OpenBSD::Ustar::BLOCKDEVICE } 744sub devicetype() { 'c' } 745 746package OpenBSD::CompactWriter; 747 748use constant { 749 FH => 0, 750 BS => 1, 751 ZEROES => 2, 752 UNFINISHED => 3, 753}; 754 755sub new 756{ 757 my ($class, $out) = @_; 758 my $bs = (stat $out)[11]; 759 my $zeroes; 760 if (defined $bs) { 761 $zeroes = "\x00"x$bs; 762 } 763 bless [ $out, $bs, $zeroes, 0 ], $class; 764} 765 766sub write 767{ 768 my ($self, $buffer) = @_; 769 my ($fh, $bs, $zeroes, $e) = @$self; 770START: 771 if (defined $bs) { 772 for (my $i = 0; $i + $bs <= length($buffer); $i+= $bs) { 773 if (substr($buffer, $i, $bs) eq $zeroes) { 774 my $r = syswrite($fh, $buffer, $i); 775 unless (defined $r && $r == $i) { 776 return 0; 777 } 778 $i+=$bs; 779 my $seek_forward = $bs; 780 while (substr($buffer, $i, $bs) eq $zeroes) { 781 $i += $bs; 782 $seek_forward += $bs; 783 } 784 defined(sysseek($fh, $seek_forward, 1)) 785 or return 0; 786 $buffer = substr($buffer, $i); 787 if (length $buffer == 0) { 788 $self->[UNFINISHED] = 1; 789 return 1; 790 } 791 goto START; 792 } 793 } 794 } 795 $self->[UNFINISHED] = 0; 796 my $r = syswrite($fh, $buffer); 797 if (defined $r && $r == length $buffer) { 798 return 1; 799 } else { 800 return 0; 801 } 802} 803 804sub close 805{ 806 my ($self) = @_; 807 if ($self->[UNFINISHED]) { 808 defined(sysseek($self->[FH], -1, 1)) or return 0; 809 defined(syswrite($self->[FH], "\0")) or return 0; 810 } 811 return 1; 812} 813 814package OpenBSD::Ustar::File; 815our @ISA=qw(OpenBSD::Ustar::Object); 816 817sub create 818{ 819 my $self = shift; 820 $self->make_basedir; 821 open(my $fh, '>', $self->fullname) or 822 $self->fatal("Can't write to #1: #2", $self->fullname, $!); 823 $self->extract_to_fh($fh); 824} 825 826sub extract_to_fh 827{ 828 my ($self, $fh) = @_; 829 my $buffer; 830 my $out = OpenBSD::CompactWriter->new($fh); 831 my $toread = $self->{size}; 832 if ($self->{partial}) { 833 $toread -= length($self->{partial}); 834 unless ($out->write($self->{partial})) { 835 $self->fatal("Error writing to #1: #2", 836 $self->fullname, $!); 837 } 838 } 839 while ($toread > 0) { 840 my $maxread = $buffsize; 841 $maxread = $toread if $maxread > $toread; 842 my $actual = read($self->{archive}{fh}, $buffer, $maxread); 843 if (!defined $actual) { 844 $self->fatal("Error reading from archive: #1", $!); 845 } 846 if ($actual == 0) { 847 $self->fatal("Premature end of archive"); 848 } 849 $self->{archive}{swallow} -= $actual; 850 unless ($out->write($buffer)) { 851 $self->fatal("Error writing to #1: #2", 852 $self->fullname, $!); 853 } 854 855 $toread -= $actual; 856 $self->left_todo($toread); 857 } 858 $self->set_modes_on_object($fh); 859 $out->close or $self->fatal("Error closing #1: #2", 860 $self->fullname, $!); 861} 862 863sub contents 864{ 865 my ($self, $lookfor) = @_; 866 my $toread = $self->{size}; 867 my $buffer; 868 my $offset = 0; 869 if ($self->{partial}) { 870 $buffer = $self->{partial}; 871 $offset = length($self->{partial}); 872 $toread -= $offset; 873 } 874 875 while ($toread != 0) { 876 my $sz = $toread; 877 if (defined $lookfor) { 878 last if (defined $buffer) and &$lookfor($buffer); 879 $sz = 1024 if $sz > 1024; 880 } 881 my $actual = read($self->{archive}{fh}, $buffer, $sz, $offset); 882 if (!defined $actual) { 883 $self->fatal("Error reading from archive: #1", $!); 884 } 885 if ($actual != $sz) { 886 $self->fatal("Error: short read from archive"); 887 } 888 $self->{archive}{swallow} -= $actual; 889 $toread -= $actual; 890 $offset += $actual; 891 } 892 893 $self->{partial} = $buffer; 894 return $buffer; 895} 896 897sub write_contents 898{ 899 my ($self, $arc) = @_; 900 my $filename = $self->{realname}; 901 my $size = $self->{size}; 902 my $out = $arc->{fh}; 903 open my $fh, "<", $filename or $self->fatal("Can't read file #1: #2", 904 $filename, $!); 905 906 my $buffer; 907 my $toread = $size; 908 while ($toread > 0) { 909 my $maxread = $buffsize; 910 $maxread = $toread if $maxread > $toread; 911 my $actual = read($fh, $buffer, $maxread); 912 if (!defined $actual) { 913 $self->fatal("Error reading from file: #1", $!); 914 } 915 if ($actual == 0) { 916 $self->fatal("Premature end of file"); 917 } 918 unless (print $out $buffer) { 919 $self->fatal("Error writing to archive: #1", $!); 920 } 921 922 $toread -= $actual; 923 $self->left_todo($toread); 924 } 925 if ($size % 512) { 926 print $out "\0" x (512 - $size % 512) or 927 $self->fatal("Error writing to archive: #1", $!); 928 } 929} 930 931sub copy_contents 932{ 933 my ($self, $arc) = @_; 934 my $out = $arc->{fh}; 935 my $buffer; 936 my $size = $self->{size}; 937 my $toread = $size; 938 while ($toread > 0) { 939 my $maxread = $buffsize; 940 $maxread = $toread if $maxread > $toread; 941 my $actual = read($self->{archive}{fh}, $buffer, $maxread); 942 if (!defined $actual) { 943 $self->fatal("Error reading from archive: #1", $!); 944 } 945 if ($actual == 0) { 946 $self->fatal("Premature end of archive"); 947 } 948 $self->{archive}{swallow} -= $actual; 949 unless (print $out $buffer) { 950 $self->fatal("Error writing to archive #1", $!); 951 } 952 953 $toread -= $actual; 954 } 955 if ($size % 512) { 956 print $out "\0" x (512 - $size % 512) or 957 $self->fatal("Error writing to archive: #1", $!); 958 } 959 $self->alias($arc, $self->name); 960} 961 962sub isFile() { 1 } 963 964sub type() { OpenBSD::Ustar::FILE1 } 965 9661; 967