File Coverage

blib/lib/VirtualFS/ISO9660.pm
Criterion Covered Total %
statement 202 247 81.7
branch 62 118 52.5
condition 7 14 50.0
subroutine 32 38 84.2
pod 5 7 71.4
total 308 424 72.6


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             }