| 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
|
|
|
|
|
|
|
} |