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