| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #!/usr/bin/perl -l | 
| 2 |  |  |  |  |  |  | package VirtualFS::ISO9660; | 
| 3 |  |  |  |  |  |  | require 5.005_003;	# only tested on 5.8.0. | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 1 |  |  | 1 |  | 35053 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 6 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 37 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 1 |  |  | 1 |  | 6 | use Scalar::Util qw(dualvar); | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 233 |  | 
| 9 | 1 |  |  | 1 |  | 7 | use File::Spec; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 46 |  | 
| 10 | 1 |  |  | 1 |  | 4 | use Carp qw(carp croak); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 60 |  | 
| 11 | 1 |  |  | 1 |  | 4 | use Fcntl ':mode'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 323 |  | 
| 12 | 1 |  |  | 1 |  | 926 | use Symbol;	# need geniosym | 
|  | 1 |  |  |  |  | 1151 |  | 
|  | 1 |  |  |  |  | 124 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | # for debugging | 
| 15 |  |  |  |  |  |  | #require Data::Dumper; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | our $VERSION = 0.02; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | our ($SEPARATOR_1, $SEPARATOR_2, $A_CHARACTERS, $D_CHARACTERS); | 
| 20 | 1 |  |  | 1 |  | 8 | { no strict 'vars'; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 80 |  | 
| 21 |  |  |  |  |  |  | *SEPARATOR_1 = \ '.'; | 
| 22 |  |  |  |  |  |  | *SEPARATOR_2 = \ ';'; | 
| 23 |  |  |  |  |  |  | *D_CHARACTERS = \ '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_'; | 
| 24 |  |  |  |  |  |  | *A_CHARACTERS = \ q# !"%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_#; | 
| 25 |  |  |  |  |  |  | } | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # see ECMA-119 for official ISO9660 format (available free of charge) | 
| 29 |  |  |  |  |  |  | # http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 1 |  |  | 1 |  | 6 | use constant { CDROM_SECTOR_SIZE => 2048, VOLUME_DESCRIPTOR_SECTOR => 16 }; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 2491 |  | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | sub new { | 
| 34 | 1 |  |  | 1 | 1 | 30 | my $class = shift; | 
| 35 | 1 | 50 |  |  |  | 4 | my $filename = shift or croak "No filename specified for " . __PACKAGE__ . "->new"; | 
| 36 | 1 |  |  |  |  | 4 | my %options = @_;	# rest is in hash format | 
| 37 | 1 | 50 |  |  |  | 77 | CORE::open (my $fh, '<', $filename) or return;	# let *them* handle open failures! | 
| 38 | 1 |  |  |  |  | 4 | binmode $fh; | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 1 |  |  |  |  | 2 | my $buffer; | 
| 41 |  |  |  |  |  |  | # try not to croak() unless it's the fault of the caller. | 
| 42 |  |  |  |  |  |  | # that means, among other things, simply return undef (indicating an error) | 
| 43 |  |  |  |  |  |  | # when the format of the ISO is invalid. | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | # read the boot-record volume descriptor | 
| 46 | 1 | 50 |  |  |  | 6 | __readsectors($fh, $buffer, VOLUME_DESCRIPTOR_SECTOR) or return; | 
| 47 | 1 |  |  |  |  | 5 | my $voldesc = __extract_voldesc($buffer); | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | # read the path table | 
| 50 |  |  |  |  |  |  | # the path table is, for whatever reason, a brief listing of every directory | 
| 51 |  |  |  |  |  |  | # on the disc.  There are efefctively three copies of this; one has its integers | 
| 52 |  |  |  |  |  |  | # MSB-first, one has them LSB-first, and the third would be the actual complete | 
| 53 |  |  |  |  |  |  | # pile of directory entries. | 
| 54 | 1 |  |  |  |  | 39 | __readsectors($fh, $buffer, $voldesc->{lpathlocation}, | 
| 55 |  |  |  |  |  |  | int (($voldesc->{pathtablesize} + CDROM_SECTOR_SIZE - 1) / CDROM_SECTOR_SIZE)); | 
| 56 | 1 |  |  |  |  | 5 | my $pathtree = __build_pathtree(__extract_pathtable($buffer, $voldesc->{pathtablesize})); | 
| 57 |  |  |  |  |  |  | #print Data::Dumper::Dumper($pathtree); | 
| 58 | 1 |  |  |  |  | 12 | bless [$fh, $voldesc, $pathtree], $class; | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | # open a fake directory handle. $dirh->readdir() will do what you think it would. | 
| 62 |  |  |  |  |  |  | # opendir(dirh, path); | 
| 63 |  |  |  |  |  |  | # opendir(dirh, '/foo/bar/baz') opens /foo/bar/baz | 
| 64 |  |  |  |  |  |  | # opendir(dirh, '/foo/bar/baz/') opens /foo/bar/baz | 
| 65 |  |  |  |  |  |  | # opendir(dirh, 'foo/bar/baz') opens /foo/bar/baz | 
| 66 |  |  |  |  |  |  | sub opendir { | 
| 67 | 2 |  |  | 2 | 1 | 3 | my $this = shift; | 
| 68 | 2 |  |  |  |  | 3 | my $loc; | 
| 69 | 2 |  |  |  |  | 19 | my $treepos = $this->[2]; | 
| 70 | 2 |  |  |  |  | 6 | my (undef, $path) = @_; | 
| 71 | 2 |  |  |  |  | 23 | my @parts = grep {!/^$/} File::Spec->splitdir($path);	# ignore blank parts | 
|  | 8 |  |  |  |  | 23 |  | 
| 72 | 2 | 100 |  |  |  | 7 | if (@parts) { | 
| 73 | 1 |  |  |  |  | 3 | for (@parts) { | 
| 74 | 4 | 50 |  |  |  | 14 | unless ($treepos = $treepos->[1]{+uc}) { | 
| 75 | 0 |  |  |  |  | 0 | $! = "Path part not found: $_"; | 
| 76 | 0 |  |  |  |  | 0 | return; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  | } | 
| 79 | 1 |  |  |  |  | 3 | $loc = $treepos->[0]; | 
| 80 |  |  |  |  |  |  | } else { | 
| 81 |  |  |  |  |  |  | # treat the root directory specially | 
| 82 | 1 |  |  |  |  | 4 | $loc = $this->[1]{rootdir}{location}; | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # FIXME: use File::Spec | 
| 86 | 2 |  |  |  |  | 19 | $_[0] = VirtualFS::ISO9660::DirHandle->__new($this->[0], $loc, $this, join('/', @parts) ); | 
| 87 |  |  |  |  |  |  | } | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | sub open { | 
| 90 | 2 |  |  | 2 | 0 | 3 | my $this = shift; | 
| 91 | 2 | 50 |  |  |  | 9 | croak "need 3-argument open" unless @_ == 3; | 
| 92 | 2 | 50 |  |  |  | 7 | croak "2nd arg must be '<'" unless $_[1] eq '<'; | 
| 93 | 2 | 50 |  |  |  | 10 | my @stats = $this->stat($_[2]) or croak "can't stat $_[2]: $!"; | 
| 94 | 2 | 50 |  |  |  | 12 | croak "can't open() a directory" if S_ISDIR($stats[2]); | 
| 95 | 2 |  |  |  |  | 11 | $_[0] = Symbol::geniosym(); | 
| 96 | 2 | 50 |  |  |  | 61 | tie( *{$_[0]}, 'VirtualFS::ISO9660::FileHandle', $this->[0], $stats[1], $this) | 
|  | 2 |  |  |  |  | 30 |  | 
| 97 |  |  |  |  |  |  | and return 1; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | sub stat { | 
| 101 | 2 |  |  | 2 | 1 | 3 | my $this = shift; | 
| 102 | 2 |  |  |  |  | 5 | my $filename = uc shift; # note the call to uc; ISO9660 names are all UPPERCASE | 
| 103 | 2 |  |  |  |  | 4 | my $ref; | 
| 104 |  |  |  |  |  |  | my $version; | 
| 105 |  |  |  |  |  |  | # FIXME: use File::Spec | 
| 106 | 2 | 50 |  |  |  | 33 | $filename = '/'.$filename unless $filename =~ m#^/#; | 
| 107 | 2 | 50 |  |  |  | 8 | if ($filename =~ s/;(.*)//) { | 
| 108 | 0 |  |  |  |  | 0 | $version = $1-1; | 
| 109 |  |  |  |  |  |  | } | 
| 110 | 2 | 50 |  |  |  | 9 | unless (exists($this->[4]{$filename})) { | 
| 111 | 2 |  |  |  |  | 56 | my (undef, $path, undef) = File::Spec->splitpath($filename); | 
| 112 | 2 | 50 |  |  |  | 10 | $this->opendir(my $dirh, $path) or croak "can't open path $path: $!"; | 
| 113 | 2 |  |  |  |  | 14 | () = $dirh->readdir();	# in list context -- this will read thru the entire dir, populating the cache | 
| 114 | 2 | 50 |  |  |  | 39 | croak "can't find file $filename" unless exists($this->[4]{$filename}); | 
| 115 |  |  |  |  |  |  | } | 
| 116 | 2 |  |  |  |  | 4 | $ref = $this->[4]{$filename}; | 
| 117 | 2 | 50 |  |  |  | 7 | unless (defined($version)) { $version = $#$ref; } | 
|  | 2 |  |  |  |  | 4 |  | 
| 118 | 2 | 50 |  |  |  | 6 | croak "version $version of $filename doesn't exist" unless defined $ref->[$version]; | 
| 119 | 2 |  |  |  |  | 4 | $ref = $ref->[$version][1]; | 
| 120 | 2 |  |  |  |  | 6 | return $this->__stat($ref); | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # ============================================================ | 
| 125 |  |  |  |  |  |  | #                         accessors | 
| 126 |  |  |  |  |  |  | # ============================================================ | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # $o->identifier() | 
| 129 |  |  |  |  |  |  | #		returns a hash containing the keys 'system', 'volume', | 
| 130 |  |  |  |  |  |  | #		'volume_set', 'publisher', 'preparer', and 'application', | 
| 131 |  |  |  |  |  |  | #		as well as their corresponding values (of course). | 
| 132 |  |  |  |  |  |  | #	$o->identifier(key) | 
| 133 |  |  |  |  |  |  | #		assuming 'key' matches one of the above keys, returns the | 
| 134 |  |  |  |  |  |  | #		value for that key. | 
| 135 |  |  |  |  |  |  | # $o->identifier(key1, key2, key3) | 
| 136 |  |  |  |  |  |  | #			assuming that key1,key2,key3 each match one of the above keys, | 
| 137 |  |  |  |  |  |  | #		returns a list containing the values for those keys, in the | 
| 138 |  |  |  |  |  |  | #		same order. | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub identifier { | 
| 141 | 1 |  |  | 1 | 1 | 454 | my $this = shift; | 
| 142 | 1 | 50 |  |  |  | 5 | if (@_ == 0) { | 
| 143 |  |  |  |  |  |  | # return a hashref | 
| 144 | 1 |  |  |  |  | 3 | my %h; | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 1 |  |  |  |  | 12 | @h{'system', 'volume', 'volume_set', 'publisher', 'preparer', 'application'} = | 
| 147 | 1 |  |  |  |  | 3 | @{$this->[1]}{'system_id', 'volume_id', 'volume_set_id', 'publisher_id', 'preparer_id', 'application_id'}; | 
| 148 | 1 |  |  |  |  | 14 | return %h; | 
| 149 |  |  |  |  |  |  | } else { | 
| 150 | 0 |  |  |  |  | 0 | my @list = @{$this->[1]}{ map "$_\_id", @_ }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 151 | 0 | 0 |  |  |  | 0 | return wantarray?@list:pop@list; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | # $o->id_file() | 
| 157 |  |  |  |  |  |  | #		See the 'identifier' method; only, the keys here are: | 
| 158 |  |  |  |  |  |  | #		'copyright', 'abstract', and 'biblio'. | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | sub id_file { | 
| 161 | 3 |  |  | 3 | 1 | 1573 | my $this = shift; | 
| 162 | 3 | 100 |  |  |  | 11 | if (@_ == 0) { | 
| 163 |  |  |  |  |  |  | # return a hashref | 
| 164 | 1 |  |  |  |  | 2 | my %h; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 1 |  |  |  |  | 18 | @h{'copyright', 'abstract', 'biblio'} = | 
| 167 | 1 |  |  |  |  | 2 | @{$this->[1]}{'copyright_file', 'abstract_file', 'biblio_file'}; | 
| 168 | 1 |  |  |  |  | 13 | return %h; | 
| 169 |  |  |  |  |  |  | } else { | 
| 170 | 2 |  |  |  |  | 11 | my @list = @{$this->[1]}{ map "$_\_file", @_ }; | 
|  | 2 |  |  |  |  | 8 |  | 
| 171 | 2 | 50 |  |  |  | 16 | return wantarray?@list:pop@list; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | # $o->extract_file() | 
| 176 |  |  |  |  |  |  | # 		$o->extract_file('/COPYRIGH', 'to-file'); | 
| 177 |  |  |  |  |  |  | #	This is done using CORE::open on the to-file, which means that | 
| 178 |  |  |  |  |  |  | # in perl 5.8.0 you can do: | 
| 179 |  |  |  |  |  |  | #			$o->extract_file('/COPYRIGH', \$scalar); | 
| 180 |  |  |  |  |  |  | #	and the contents of the file will be extracted into $scalar. | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub extract_file { | 
| 183 | 0 |  |  | 0 | 0 | 0 | my $this = shift; | 
| 184 | 0 | 0 |  |  |  | 0 | croak 'usage: extract_file(iso-filename, output-filename)' unless @_>=2; | 
| 185 | 0 |  |  |  |  | 0 | my $from = shift; | 
| 186 | 0 |  |  |  |  | 0 | my $to = shift; | 
| 187 | 0 | 0 |  |  |  | 0 | $this->open(my $infh, '<', $from) or return;	# eh, right now open() will croak anyway. | 
| 188 | 0 |  |  |  |  | 0 | CORE::open(my $outfh, '>', $to); | 
| 189 | 0 |  |  |  |  | 0 | local $\;	# don't let $\ screw with us | 
| 190 | 0 |  |  |  |  | 0 | while(read($infh, my $buf, 4096)) { print $outfh $buf; } | 
|  | 0 |  |  |  |  | 0 |  | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | # ============================================================ | 
| 194 |  |  |  |  |  |  | #                    internal functions | 
| 195 |  |  |  |  |  |  | # ============================================================ | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | # read a sector or sectors from the image | 
| 200 |  |  |  |  |  |  | # usage: __readsectors(filehandle, buffer, start[, count]) | 
| 201 |  |  |  |  |  |  | # count defaults to 1 if not specified. And don't specify a 0. | 
| 202 |  |  |  |  |  |  | # | 
| 203 |  |  |  |  |  |  | # on success, returns 1   (a partial read is considered failure) | 
| 204 |  |  |  |  |  |  | # on failure, returns undef | 
| 205 |  |  |  |  |  |  | sub __readsectors { | 
| 206 | 2 |  | 100 | 2 |  | 12 | my $count = $_[3] || 1; | 
| 207 | 2 | 50 |  |  |  | 24 | unless (seek($_[0], $_[2] * CDROM_SECTOR_SIZE, 0)) { return } | 
|  | 0 |  |  |  |  | 0 |  | 
| 208 | 2 |  |  |  |  | 73 | my $ret = read($_[0], $_[1], $count * CDROM_SECTOR_SIZE); | 
| 209 | 2 | 50 |  |  |  | 9 | unless ($ret == $count * CDROM_SECTOR_SIZE) { return } | 
|  | 0 |  |  |  |  | 0 |  | 
| 210 | 2 |  |  |  |  | 6 | return 1; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | # path table record (ECMA-119 section 9.4) | 
| 214 |  |  |  |  |  |  | # see extract_direntry and extrapolate for basic use | 
| 215 |  |  |  |  |  |  | sub __extract_pathtablerec { | 
| 216 | 5 |  |  | 5 |  | 10 | my %h; | 
| 217 | 5 | 50 |  |  |  | 12 | my $sref = ref($_[0])?$_[0]:\$_[0]; | 
| 218 | 5 |  |  |  |  | 10 | my $len = unpack('C', $$sref); | 
| 219 | 5 |  |  |  |  | 42 | @h{'LEN-EAR', 'location', 'parent', 'name'} = | 
| 220 |  |  |  |  |  |  | unpack("x C V v A$len x![v]", $$sref); | 
| 221 |  |  |  |  |  |  |  | 
| 222 | 5 | 50 |  |  |  | 18 | if (ref $_[0]) { | 
| 223 | 5 |  |  |  |  | 8 | my $totallen = 1 + 1 + 4 + 2 + $len + ($len&1); | 
| 224 | 5 | 50 |  |  |  | 14 | ${$_[1]} -= $totallen if ref $_[1]; | 
|  | 5 |  |  |  |  | 11 |  | 
| 225 | 5 |  |  |  |  | 20 | substr($$sref, 0, $totallen, ''); | 
| 226 |  |  |  |  |  |  | } | 
| 227 | 5 |  |  |  |  | 24 | return \%h; | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # extract_pathtable($scalar, $pathtablesize) | 
| 231 |  |  |  |  |  |  | #   extracts all the path table entries from $scalar | 
| 232 |  |  |  |  |  |  | #	also, there'd sure as hell better be $pathtablesize bytes worth of entries | 
| 233 |  |  |  |  |  |  | #	in there... | 
| 234 |  |  |  |  |  |  | # in scalar context, returns an arrayref | 
| 235 |  |  |  |  |  |  | sub __extract_pathtable { | 
| 236 | 1 |  |  | 1 |  | 3 | my @table; | 
| 237 | 1 |  |  |  |  | 2 | my $data = shift; | 
| 238 | 1 |  |  |  |  | 2 | my $left = shift; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 1 |  |  |  |  | 9 | push @table, __extract_pathtablerec(\$data, \$left) | 
| 241 |  |  |  |  |  |  | while $left>0; | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 1 |  |  |  |  | 6 | return \@table; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | # build_pathtree(\@array) | 
| 247 |  |  |  |  |  |  | # 	returns a convenient hashref of all the directories. | 
| 248 |  |  |  |  |  |  | sub __build_pathtree { | 
| 249 | 1 |  |  | 1 |  | 3 | my $h; | 
| 250 |  |  |  |  |  |  | my @hrefs; | 
| 251 | 1 |  |  |  |  | 2 | my $i=0; | 
| 252 | 1 |  |  |  |  | 2 | for (@{$_[0]}) { | 
|  | 1 |  |  |  |  | 4 |  | 
| 253 | 5 | 100 |  |  |  | 12 | unless (@hrefs) {	# special case: the root directory | 
| 254 | 1 |  |  |  |  | 5 | $hrefs[0] = $h = [$_->{parent}]; | 
| 255 | 1 |  |  |  |  | 1 | $i++; | 
| 256 | 1 |  |  |  |  | 3 | next; | 
| 257 |  |  |  |  |  |  | } | 
| 258 | 4 |  |  |  |  | 23 | $hrefs[$_->{parent} - 1][1]{ $_->{name} } = | 
| 259 |  |  |  |  |  |  | $hrefs[$i] = [ $_->{location} ]; | 
| 260 | 4 |  |  |  |  | 9 | $i++; | 
| 261 |  |  |  |  |  |  | } | 
| 262 | 1 |  |  |  |  | 4 | return $h; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | # directory record (ECMA-119 section 9.1) | 
| 266 |  |  |  |  |  |  | # 		extract_direntry($scalar) | 
| 267 |  |  |  |  |  |  | # returns a happy hashref. | 
| 268 |  |  |  |  |  |  | # | 
| 269 |  |  |  |  |  |  | # alternatively, you can do: | 
| 270 |  |  |  |  |  |  | #		 __extract_direntry(\$scalar) | 
| 271 |  |  |  |  |  |  | # which, in addition to returning the hashref, eats the directory | 
| 272 |  |  |  |  |  |  | # entry out of $scalar. | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | sub __extract_direntry { | 
| 275 | 8 |  |  | 8 |  | 10 | my %h; | 
| 276 | 8 | 50 |  |  |  | 17 | my $sref = ref($_[0])?$_[0]:\$_[0];	# make sure we have a reference to ease unpacking | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 8 |  |  |  |  | 88 | @h{'LEN-DR', 'LEN-EAR', 'location', 'size', 'time', 'flags', 'unitsize', | 
| 279 |  |  |  |  |  |  | 'gapsize', 'volseqnum', 'name'} = unpack( | 
| 280 |  |  |  |  |  |  | 'C C Vx[N] Vx[N] a7 C C C vx[n] C/a', $$sref); | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | # if they gave us a reference, eat the data out of the scalar. | 
| 283 | 8 | 50 |  |  |  | 27 | if (ref $_[0]) { substr($$sref, 0, $h{'LEN-DR'}, ''); } | 
|  | 0 |  |  |  |  | 0 |  | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 8 |  |  |  |  | 20 | return \%h; | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | # volume descriptor (ECMA-119 section 8) | 
| 289 |  |  |  |  |  |  | # __extract_voldesc($scalar) | 
| 290 |  |  |  |  |  |  | sub __extract_voldesc { | 
| 291 | 1 |  |  | 1 |  | 1 | my %h; | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 1 |  |  |  |  | 21 | @h{'type', 'stdid', 'version'} = | 
| 294 |  |  |  |  |  |  | unpack('CA5C', $_[0]); | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | # how we grok the rest depends on the type. | 
| 297 |  |  |  |  |  |  | # 0=Boot record | 
| 298 |  |  |  |  |  |  | # 1=Primary volume descriptor | 
| 299 |  |  |  |  |  |  | # 2=Supplementary volume descriptor | 
| 300 |  |  |  |  |  |  | # 3=Volume partition descriptor | 
| 301 |  |  |  |  |  |  | # 4-254=RFU | 
| 302 |  |  |  |  |  |  | # 255=Volume descriptor set terminator | 
| 303 |  |  |  |  |  |  |  | 
| 304 | 1 | 50 |  |  |  | 8 | if ($h{type} == 0) { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | # section 8.2: boot record | 
| 306 | 0 |  |  |  |  | 0 | @h{'sysid','bootid'} = unpack('x7A32A32', $_[0]); | 
| 307 |  |  |  |  |  |  | } elsif ($h{type} == 1) { | 
| 308 |  |  |  |  |  |  | # section 8.4: primary volume descriptor | 
| 309 | 1 |  |  |  |  | 37 | @h{'system_id', 'volume_id', 'size', 'setsize', 'seqnum', 'blocksize', | 
| 310 |  |  |  |  |  |  | 'pathtablesize', 'lpathlocation', 'optlpathlocation', | 
| 311 |  |  |  |  |  |  | #'mpathlocation', 'optmpathlocation', | 
| 312 |  |  |  |  |  |  | 'rootdir', | 
| 313 |  |  |  |  |  |  | 'volume_set_id', 'publisher_id', 'preparer_id', 'application_id', | 
| 314 |  |  |  |  |  |  | 'copyright_file', 'abstract_file', 'biblio_file', | 
| 315 |  |  |  |  |  |  | 'create_time', 'modify_time', 'expire_time', 'effective_time', | 
| 316 |  |  |  |  |  |  | 'format_version'} = unpack(q{ | 
| 317 |  |  |  |  |  |  | x7		# skip over the 7 bytes we pulled out at the very beginning | 
| 318 |  |  |  |  |  |  | x		# byte 8 is RFU and should be 0 in the Primary Volume Descriptor | 
| 319 |  |  |  |  |  |  | # (probably for alignment purposes) | 
| 320 |  |  |  |  |  |  | A32		# System Identifier | 
| 321 |  |  |  |  |  |  | A32		# Volume Identifier | 
| 322 |  |  |  |  |  |  | x8		# RFU, should be 0 | 
| 323 |  |  |  |  |  |  | V		# Volume Space Size | 
| 324 |  |  |  |  |  |  | x[N]	# Volume Space Size again, only in Motorola order | 
| 325 |  |  |  |  |  |  | x32		# another RFU | 
| 326 |  |  |  |  |  |  | vx[n]	# Volume Set Size and its motorola form | 
| 327 |  |  |  |  |  |  | vx[n]	# Volume Sequence Number | 
| 328 |  |  |  |  |  |  | vx[n]	# Logical Block Size | 
| 329 |  |  |  |  |  |  | Vx[N]	# Path Table Size | 
| 330 |  |  |  |  |  |  | V		# Type L path table location | 
| 331 |  |  |  |  |  |  | V		# Type L path table location (Optional) | 
| 332 |  |  |  |  |  |  | x[N]	# Type M path table location | 
| 333 |  |  |  |  |  |  | x[N]	# Type M path table location (Optional) | 
| 334 |  |  |  |  |  |  | a34		# 'Directory Record for Root Directory' (??? wtf?) | 
| 335 |  |  |  |  |  |  | A128	# Volume Set Identifier | 
| 336 |  |  |  |  |  |  | A128	# Publisher Identifier | 
| 337 |  |  |  |  |  |  | A128	# Data Preparer Identifier | 
| 338 |  |  |  |  |  |  | A128	# Application Identifier | 
| 339 |  |  |  |  |  |  | A37		# Copyright File Identifier | 
| 340 |  |  |  |  |  |  | A37		# Abstract File Identifier | 
| 341 |  |  |  |  |  |  | A37		# Bibliographic File Identifier | 
| 342 |  |  |  |  |  |  | a17		# Volume Creation Timestamp | 
| 343 |  |  |  |  |  |  | a17		# Volume Modification Timestamp | 
| 344 |  |  |  |  |  |  | a17		# Volume Expiration Timestamp | 
| 345 |  |  |  |  |  |  | a17		# Volume Effective Timestamp | 
| 346 |  |  |  |  |  |  | C		# File Structure Version | 
| 347 |  |  |  |  |  |  | x		# RFU | 
| 348 |  |  |  |  |  |  | }, $_[0]); | 
| 349 |  |  |  |  |  |  |  | 
| 350 | 1 |  |  |  |  | 7 | $h{rootdir} = __extract_direntry($h{rootdir}); | 
| 351 |  |  |  |  |  |  | } elsif ($h{type} == 2) { | 
| 352 |  |  |  |  |  |  | # section 8.5, Supplementary Volume Descriptor | 
| 353 |  |  |  |  |  |  | # gahhhh... | 
| 354 |  |  |  |  |  |  | } elsif ($h{type} == 3) { | 
| 355 |  |  |  |  |  |  | # section 8.6, Volume Partition Descriptor | 
| 356 | 0 |  |  |  |  | 0 | @h{'sysid', 'partition_id', 'partition_location', 'partition_size'} = | 
| 357 |  |  |  |  |  |  | unpack('x7xA32A32Vx[N]Vx[N]', $_[0]); | 
| 358 |  |  |  |  |  |  | } | 
| 359 | 1 |  |  |  |  | 3 | return \%h; | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | # $obj->__startpos('/path/to/filename') | 
| 363 |  |  |  |  |  |  | # returns the offset into the .ISO file where you can find the contents of that | 
| 364 |  |  |  |  |  |  | # file (for debugging purposes). | 
| 365 |  |  |  |  |  |  | sub __startpos { | 
| 366 | 0 |  |  | 0 |  | 0 | my $this = shift; | 
| 367 | 0 |  |  |  |  | 0 | my @x = $this->stat($_[0]); | 
| 368 | 0 | 0 |  |  |  | 0 | return undef unless @x;	# no data? give up. | 
| 369 |  |  |  |  |  |  | # $x[1] will point to the info object | 
| 370 | 0 |  |  |  |  | 0 | return ($x[1]{location} * CDROM_SECTOR_SIZE); | 
| 371 |  |  |  |  |  |  | } | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | sub __stat { | 
| 374 | 2 |  |  | 2 |  | 3 | my $this = shift; | 
| 375 | 2 |  |  |  |  | 3 | my $ref = shift; | 
| 376 |  |  |  |  |  |  |  | 
| 377 | 2 |  |  |  |  | 3 | my $perms = S_IRUSR|S_IRGRP|S_IROTH;	# everybody can read | 
| 378 |  |  |  |  |  |  | # nobody can write (ISO9660 is readonly) | 
| 379 |  |  |  |  |  |  | # nobody can execute (how's it gonna be executed?) | 
| 380 |  |  |  |  |  |  |  | 
| 381 | 2 | 50 |  |  |  | 7 | if ($ref->{flags} & 2) { | 
| 382 | 0 |  |  |  |  | 0 | $perms |= S_IFDIR; | 
| 383 |  |  |  |  |  |  | } else { | 
| 384 | 2 |  |  |  |  | 5 | $perms |= S_IFREG; | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | return ( | 
| 388 | 2 |  |  |  |  | 19 | $this,				# "device number", return this object | 
| 389 |  |  |  |  |  |  | $ref,				# "inode number", return the cache ref | 
| 390 |  |  |  |  |  |  | $perms,				# permissions | 
| 391 |  |  |  |  |  |  | 1,					# number of hard links | 
| 392 |  |  |  |  |  |  | 0,					# uid | 
| 393 |  |  |  |  |  |  | 0,					# gid | 
| 394 |  |  |  |  |  |  | 0,					# rdev(???) | 
| 395 |  |  |  |  |  |  | $ref->{size}, 		# size | 
| 396 |  |  |  |  |  |  | 0,					# atime | 
| 397 |  |  |  |  |  |  | 0,					# mtime | 
| 398 |  |  |  |  |  |  | 0,					# ctime | 
| 399 |  |  |  |  |  |  | CDROM_SECTOR_SIZE,	# blksize | 
| 400 |  |  |  |  |  |  | int(($ref->{size} + CDROM_SECTOR_SIZE - 1) / CDROM_SECTOR_SIZE),	# block count | 
| 401 |  |  |  |  |  |  | ); | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | package VirtualFS::ISO9660::DirHandle; | 
| 405 |  |  |  |  |  |  |  | 
| 406 | 1 |  |  | 1 |  | 8 | use Scalar::Util qw(dualvar); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 60 |  | 
| 407 | 1 |  |  | 1 |  | 5 | use constant { CDROM_SECTOR_SIZE => 2048 }; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 677 |  | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | *__extract_direntry = \&VirtualFS::ISO9660::__extract_direntry; | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | # new (iso_filehandle, sector, ISO9660 object, pathname) | 
| 412 |  |  |  |  |  |  | # pathname won't start with '/', nor will it end with one. | 
| 413 |  |  |  |  |  |  | sub __new { | 
| 414 | 2 |  |  | 2 |  | 5 | my $class = shift; | 
| 415 | 2 |  |  |  |  | 5 | my ($fromfh, $sector, $parent, $name) = @_; | 
| 416 |  |  |  |  |  |  |  | 
| 417 | 2 | 50 |  |  |  | 74 | CORE::open(my $fh, '<&', $fromfh) or return; | 
| 418 | 2 |  |  |  |  | 15 | seek($fh, $sector * CDROM_SECTOR_SIZE, 0); | 
| 419 |  |  |  |  |  |  | # / (root) and /any/dir/here are different in that the former | 
| 420 |  |  |  |  |  |  | # ends in /, while the latter does not.  This causes confusion. | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | # FIXME: use File::Spec | 
| 423 | 2 | 100 |  |  |  | 8 | $name = '/'.$name if $name ne ''; | 
| 424 | 2 |  |  |  |  | 24 | bless [$fh, $sector, 0, $parent, $name, undef], $class; | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | sub rewinddir { | 
| 428 | 0 |  |  | 0 |  | 0 | my $this = shift; | 
| 429 | 0 |  |  |  |  | 0 | $this->[2] = 0; | 
| 430 | 0 |  |  |  |  | 0 | seek($this->[0], $this->[1] * CDROM_SECTOR_SIZE, 0); | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | # merely for completeness | 
| 434 | 0 |  |  | 0 |  | 0 | sub closedir {} | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub readdir { | 
| 437 | 2 | 50 |  | 2 |  | 6 | if (wantarray) { | 
| 438 | 2 |  |  |  |  | 3 | my $this = shift; | 
| 439 | 2 |  |  |  |  | 3 | my @x; | 
| 440 |  |  |  |  |  |  | my $x; | 
| 441 | 2 |  |  |  |  | 7 | push @x, $x while $x=$this->__readdir; | 
| 442 | 2 |  |  |  |  | 10 | return @x; | 
| 443 |  |  |  |  |  |  | } else { | 
| 444 | 0 |  |  |  |  | 0 | goto &__readdir; | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  | } | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | sub __readdir { | 
| 449 |  |  |  |  |  |  | # $this-> | 
| 450 |  |  |  |  |  |  | # [0] = filehandle of ISO image | 
| 451 |  |  |  |  |  |  | # [1] = sector to start at | 
| 452 |  |  |  |  |  |  | # [2] = byte offset within directory | 
| 453 |  |  |  |  |  |  | # [3] = VirtualFS::ISO9660 object that spawned us (used for caching) | 
| 454 |  |  |  |  |  |  | # [4] = path of directory, parts separated by '/' and ending with '/' | 
| 455 |  |  |  |  |  |  | # [5] = total size of directory, undef if we don't know it yet. | 
| 456 |  |  |  |  |  |  |  | 
| 457 | 9 |  |  | 9 |  | 11 | my ($buf, $len); | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | # check EOF (err, EOD) | 
| 460 | 9 | 50 | 66 |  |  | 45 | return if (defined($_[0][5]) && $_[0][5] <= $_[0][2]); | 
| 461 |  |  |  |  |  |  |  | 
| 462 | 9 | 50 |  |  |  | 92 | read($_[0][0], $len, 1)==1 or return;	# find out the size of the entry | 
| 463 | 9 |  |  |  |  | 18 | $len = unpack('C',$len); | 
| 464 | 9 | 100 |  |  |  | 23 | return unless $len;						# I can't find what officially marks the end, | 
| 465 |  |  |  |  |  |  | # but this seems to work | 
| 466 |  |  |  |  |  |  |  | 
| 467 | 7 | 50 |  |  |  | 70 | seek($_[0][0], -1, 1)   or return; | 
| 468 | 7 |  |  |  |  | 13 | my $where = tell($_[0][0]); | 
| 469 | 7 | 50 |  |  |  | 143 | read($_[0][0], $buf, $len)==$len or return; | 
| 470 | 7 |  |  |  |  | 13 | $_[0][2] += $len; | 
| 471 | 7 |  |  |  |  | 13 | my $info = __extract_direntry($buf); | 
| 472 |  |  |  |  |  |  | # cache the location of this file for future reference | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | # if there's a version (;), extract it. | 
| 475 | 7 | 100 |  |  |  | 27 | if ($info->{name} =~ s/;(.*)//) { | 
| 476 | 2 |  |  |  |  | 9 | $info->{version} = $1-1; | 
| 477 |  |  |  |  |  |  | } else { | 
| 478 | 5 |  |  |  |  | 36 | $info->{version} = dualvar(0, ''); # this is equivalent to, but distinguishable from, an explicit version of 1. | 
| 479 |  |  |  |  |  |  | } | 
| 480 | 7 |  |  |  |  | 14 | $info->{name} =~ s/\.$//; # remove any trailing .'s | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | # if $this->[5] is undef, then this is the very first entry in the directory. | 
| 483 | 7 | 100 |  |  |  | 28 | if ($info->{name} eq "\c@") { | 
|  |  | 100 |  |  |  |  |  | 
| 484 | 2 |  |  |  |  | 5 | $_[0][5] = $info->{size}; | 
| 485 | 2 |  |  |  |  | 9 | $info->{name} = '.'; | 
| 486 | 2 | 100 |  |  |  | 7 | if ($_[0][4] eq '') { | 
| 487 |  |  |  |  |  |  | # special case to cache the root directory | 
| 488 | 1 |  |  |  |  | 12 | $_[0][3][4]{'/'}[$info->{version}] = [$where, $info]; | 
| 489 |  |  |  |  |  |  | } | 
| 490 |  |  |  |  |  |  | } elsif ($info->{name} eq "\cA") { | 
| 491 | 2 |  |  |  |  | 4 | $info->{name} = '..'; | 
| 492 |  |  |  |  |  |  | } else { | 
| 493 |  |  |  |  |  |  | # not a special name; cache this entry. | 
| 494 |  |  |  |  |  |  | # FIXME: use File::Spec | 
| 495 | 3 |  |  |  |  | 21 | $_[0][3][4]{$_[0][4] . '/' . $info->{name}}[$info->{version}] = [$where, $info]; | 
| 496 |  |  |  |  |  |  | } | 
| 497 | 7 |  |  |  |  | 44 | return $info->{name}; | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | 1; | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | package VirtualFS::ISO9660::FileHandle; | 
| 503 |  |  |  |  |  |  |  | 
| 504 | 1 |  |  | 1 |  | 7 | use constant { CDROM_SECTOR_SIZE => 2048 }; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 741 |  | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | # TIEHANDLE (iso_filehandle, info, ISO9660 object) | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | sub TIEHANDLE { | 
| 509 | 2 |  |  | 2 |  | 4 | my $class = shift; | 
| 510 | 2 |  |  |  |  | 5 | my ($fromfh, $info, $parent) = @_; | 
| 511 | 2 | 50 |  |  |  | 50 | open(my $fh, '<&', $fromfh) or return; | 
| 512 | 2 | 50 |  |  |  | 18 | seek($fh, $info->{location} * CDROM_SECTOR_SIZE, 0) or return; | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 2 |  |  |  |  | 31 | bless [$fh, $info, $parent, | 
| 515 |  |  |  |  |  |  | $info->{location} * CDROM_SECTOR_SIZE,		# byte 0 is here | 
| 516 |  |  |  |  |  |  | $info->{location} * CDROM_SECTOR_SIZE + $info->{size} # EOF is here | 
| 517 |  |  |  |  |  |  | ], $class; | 
| 518 |  |  |  |  |  |  | } | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | # no need to support WRITE -- the ISO format is read-only except when it's being | 
| 521 |  |  |  |  |  |  | # built from scratch. | 
| 522 |  |  |  |  |  |  | # Same goes for PRINT and PRINTF. | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | # We need: READ, READLINE, and GETC. | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | sub GETC { | 
| 527 | 0 |  |  | 0 |  | 0 | my $this = shift; | 
| 528 | 0 |  |  |  |  | 0 | my $ret; | 
| 529 | 0 |  |  |  |  | 0 | my $where = tell($this->[0]); | 
| 530 |  |  |  |  |  |  | # if we're "outside" the file, fail | 
| 531 | 0 | 0 | 0 |  |  | 0 | return undef unless $where >= $this->[3] && $where < $this->[4]; | 
| 532 | 0 | 0 |  |  |  | 0 | read($this->[0], $ret, 1) == 1 or return; | 
| 533 | 0 |  |  |  |  | 0 | return $ret; | 
| 534 |  |  |  |  |  |  | } | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | sub READ { | 
| 537 | 4 |  |  | 4 |  | 6 | my $this = shift; | 
| 538 |  |  |  |  |  |  | # READ(buffer, len, offset) | 
| 539 | 4 |  |  |  |  | 7 | my (undef,$len,$ofs) = @_; | 
| 540 | 4 | 100 |  |  |  | 10 | $ofs = 0 unless defined($ofs); | 
| 541 | 4 |  |  |  |  | 5 | my $b = \$_[0]; | 
| 542 |  |  |  |  |  |  | # don't read past the end of our virtual file! | 
| 543 | 4 | 50 |  |  |  | 12 | if ($len > $this->[4] - tell($this->[0])) { $len = $this->[4] - tell($this->[0]); } | 
|  | 4 |  |  |  |  | 8 |  | 
| 544 |  |  |  |  |  |  | # if $len ends up being 0 bytes, bail | 
| 545 | 4 | 100 |  |  |  | 13 | return 0 unless $len>0; | 
| 546 | 2 |  |  |  |  | 40 | return read($this->[0], $$b, $len, $ofs); | 
| 547 |  |  |  |  |  |  | } | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | # My wish: That Perl_do_readline (pp_hot.c) was nice enough to provide readline() | 
| 550 |  |  |  |  |  |  | # on tied filehandles by falling back to $obj->READ.  This would do two things: | 
| 551 |  |  |  |  |  |  | #	-> Simplify this object | 
| 552 |  |  |  |  |  |  | #	-> As it is presently implemented, future extensions to how <$fh> handles | 
| 553 |  |  |  |  |  |  | #		$RS or $/ won't work here, as we are effectively reimplementing | 
| 554 |  |  |  |  |  |  | #		Perl_do_readline() here.  If  Perl_do_readline() worked by calling our | 
| 555 |  |  |  |  |  |  | #		READ method, however, it would work fine. | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | sub __READLINE { | 
| 558 | 2 |  |  | 2 |  | 2 | my $buf; | 
| 559 | 2 |  |  |  |  | 41 | my $len = 0; | 
| 560 | 2 |  |  |  |  | 3 | my $rlen; | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | # read 4K of data at a time until we get something or run out of file. | 
| 563 | 2 |  |  |  |  | 8 | $rlen = $len = READ($_[0], $buf, 4096); | 
| 564 | 2 |  | 33 |  |  | 14 | until ($rlen==0 || (defined($/) && $buf =~ m[\Q$/]g)) {	# the g makes perl set pos() | 
|  |  |  | 66 |  |  |  |  | 
| 565 | 2 |  |  |  |  | 4 | $len += ($rlen = READ($_[0], $buf, 4096, $len)); | 
| 566 |  |  |  |  |  |  | } | 
| 567 | 2 | 50 |  |  |  | 6 | return undef if ($len == 0); # no more file! | 
| 568 | 2 | 50 |  |  |  | 11 | return $buf if ($rlen == 0); # we ate the rest of the file! | 
| 569 | 0 |  |  |  |  | 0 | $rlen = pos($buf); | 
| 570 | 0 |  |  |  |  | 0 | substr($buf, $rlen, $len-$rlen, '');	# eat the rest of the buffer | 
| 571 | 0 |  |  |  |  | 0 | seek($_[0][0], $rlen-$len, 1);			# and fix the file position | 
| 572 | 0 |  |  |  |  | 0 | return $buf; | 
| 573 |  |  |  |  |  |  | } | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | sub READLINE { | 
| 577 | 2 | 50 |  | 2 |  | 15 | if (wantarray) { | 
| 578 | 0 |  |  |  |  | 0 | my @lines; | 
| 579 |  |  |  |  |  |  | my $line; | 
| 580 | 0 |  |  |  |  | 0 | push @lines, $line while defined($line = $_[0]->__READLINE); | 
| 581 | 0 |  |  |  |  | 0 | return @lines; | 
| 582 |  |  |  |  |  |  | } | 
| 583 | 2 |  |  |  |  | 8 | goto &__READLINE; | 
| 584 |  |  |  |  |  |  | } | 
| 585 |  |  |  |  |  |  |  | 
| 586 |  |  |  |  |  |  | sub STAT { | 
| 587 | 0 |  |  | 0 |  |  | my $this = shift; | 
| 588 | 0 |  |  |  |  |  | return $this->[2]->__stat($this->[1]); | 
| 589 |  |  |  |  |  |  | } |