File Coverage

blib/lib/Mac/Finder/DSStore/BuddyAllocator.pm
Criterion Covered Total %
statement 275 293 93.8
branch 58 88 65.9
condition 18 36 50.0
subroutine 38 43 88.3
pod 10 12 83.3
total 399 472 84.5


line stmt bran cond sub pod time code
1             package Mac::Finder::DSStore::BuddyAllocator;
2              
3             =head1 NAME
4              
5             Mac::Finder::DSStore::BuddyAllocator - Allocate space within a file
6              
7             =head1 DESCRIPTION
8              
9             C
10             implements a buddy-allocation scheme within a file. It's used by
11             C to read certain files created by the Macintosh
12             Finder.
13              
14             The allocation methods do not perform any actual file I/O.
15             The contents of allocated blocks are read and written by the caller using
16             methods on C.
17             If the C and C methods are used,
18             or if the C hash is modified,
19             C must be called for the changes to be reflected in the file.
20              
21             =head1 METHODS
22              
23             =cut
24              
25 5     5   111524 use strict;
  5         9  
  5         187  
26 5     5   25 use warnings;
  5         10  
  5         140  
27 5     5   28 use Carp;
  5         11  
  5         16848  
28              
29             our($VERSION) = '1.00';
30              
31             # Debug logging. Uncomment these and all uses of them to activate.
32             # It might be nice to make this more easily switchable.
33             #our($loglevel) = 0;
34             #sub logf {
35             # print STDERR ( ' ' x $loglevel ) . sprintf($_[0], @_[1 .. $#_ ]) . "\n";
36             #}
37              
38             =head2 $allocator = Mac::Finder::DSStore::BuddyAllocator->open($fh)
39              
40             C constructs a new buddy allocator
41             and initializes its state from the information in the file.
42             The file handle is retained by the allocator for future
43             operations.
44              
45             =cut
46              
47             sub open {
48 14     14 1 4163 my($class, $fh) = @_;
49              
50 14         86 binmode($fh);
51              
52             # read the file header: 32 bytes, plus a mysterious extra
53             # four bytes at the front
54 14         31 my($fheader);
55 14 100       106 $fh->read($fheader, 4 + 0x20) == 0x24
56             or die "Can't read file header: $!";
57 13         592 my($magic1, $magic, $offset, $size, $offset2, $unk2) = unpack('N a4 NNN a16', $fheader);
58 13 50 33     366 die 'bad magic' unless $magic eq 'Bud1' and $magic1 == 1;
59 13 50       46 die 'inconsistency: two root addresses are different'
60             unless $offset == $offset2;
61              
62 13         73 my($self) = {
63             fh => $fh,
64             unk2 => $unk2,
65             fudge => 4, # add this to offsets for some unknown reason
66             };
67 13   33     89 bless($self, ref($class) || $class);
68            
69             # retrieve the root/index block which contains the allocator's
70             # book-keeping data
71 13         57 my ($rootblock) = $self->getBlock($offset, $size);
72              
73             # parse out the offsets of all the allocated blocks
74             # these are in tagged offset format (27 bits offset, 5 bits size)
75 13         55 my($offsetcount, $unk3) = $rootblock->read(8, 'NN');
76             # not sure what the word following the offset count is
77 13         41 $self->{'unk3'} = $unk3;
78             # For some reason, offsets are always stored in blocks of 256.
79 13         18 my(@offsets);
80 13         42 while($offsetcount > 0) {
81 14         40 push(@offsets, $rootblock->read(1024, 'N256'));
82 14         139 $offsetcount -= 256;
83             }
84             # 0 indicates an empty slot; don't need to keep those around
85 13         51 while($offsets[$#offsets] == 0) { pop(@offsets); }
  2730         5323  
86 13 100       38 grep { $_ = undef if $_ == 0 } @offsets;
  854         2476  
87              
88             # Next, read N key/value pairs
89 13         47 my($toccount) = $rootblock->read(4, 'N');
90 13         34 my($toc) = {
91             };
92 13         44 while($toccount--) {
93 22         58 my($len) = $rootblock->read(1, 'C');
94 22         72 my($name) = $rootblock->read($len);
95 22         65 my($value) = $rootblock->read(4, 'N');
96 22         91 $toc->{$name} = $value;
97             }
98              
99 13         38 $self->{'offsets'} = \@offsets;
100 13         76 $self->{'toc'} = $toc;
101              
102             # Finally, read the free lists.
103 13         30 my($freelists) = { };
104 13         52 for(my $width = 0; $width < 32; $width ++) {
105 416         1061 my($blkcount) = $rootblock->read(4, 'N');
106 416         901 $freelists->{$width} = [ $rootblock->read(4 * $blkcount, 'N*') ];
107             }
108 13         30 $self->{'freelist'} = $freelists;
109              
110 13         96 return $self;
111             }
112              
113             =head2 $allocator = Mac::Finder::DSStore::BuddyAllocator->new($fh)
114              
115             Similar to C, but does not read anything from the file. This
116             can be used to create a new file from scratch.
117              
118             =cut
119              
120             sub new {
121 4     4 1 2395 my($cls, $fh) = @_;
122              
123 4 100       28 binmode($fh) if defined($fh);
124              
125 4         39 my($self) = {
126             fh => $fh,
127             toc => { },
128             offsets => [ ],
129             freelist => { },
130              
131             # And the mystery meat goes here...
132             unk2 => pack('NNNN', 0x100C, 0x0087, 0x200B, 0 ),
133             unk3 => 0,
134             fudge => 4
135             };
136 4   33     39 bless($self, ref $cls || $cls);
137              
138             # All our freelists are empty...
139 4         29 foreach my $width (0 .. 30) {
140 124         369 $self->{freelist}->{$width} = [ ];
141             }
142             # ... except for a single 2GB block starting at 0
143 4         25 $self->{freelist}->{31} = [ 0 ];
144              
145             # Allocate the header block, 2^5 bytes wide
146 4         23 my($hdr) = $self->_alloc(5);
147             # it had better be at offset zero
148 4 50       23 ( $hdr == 0 ) or die;
149              
150 4         17 $self;
151             }
152              
153             =head2 $allocator->close( )
154              
155             Closes the underlying file handle.
156              
157             =cut
158              
159             sub close {
160 12     12 1 9081 my($self) = @_;
161 12         36 my($fh) = $self->{fh};
162              
163 12         79 delete $self->{fh};
164              
165 12         98 $fh->close;
166             }
167              
168             =head2 $allocator->listBlocks($verbose)
169              
170             List all the blocks in order and see if there are any gaps or overlaps.
171             If C<$verbose> is true, then the blocks are listed to the current
172             output filehandle. Returns true if the allocated and free blocks
173             have no gaps or overlaps.
174              
175             =cut
176              
177             sub listBlocks {
178 58     58 1 12734 my($self, $verbose) = @_;
179 58         84 my(%byaddr);
180 58         74 my($addr, $len);
181              
182             # We store all blocks (allocated and free) in %byaddr,
183             # then go through its keys in order
184              
185             # Store the implicit 32-byte block that holds the file header
186 58         71 push(@{$byaddr{0}}, "5 (file header)");
  58         186  
187              
188             # Store all the numbered/allocated blocks from @offsets
189 58         90 for my $blnum (0 .. $#{$self->{'offsets'}}) {
  58         184  
190 2558         3932 my($addr_size) = $self->{'offsets'}->[$blnum];
191 2558 100       6132 next unless defined $addr_size;
192 2265         2476 $addr = $addr_size & ~0x1F;
193 2265         3881 $len = $addr_size & 0x1F;
194 2265         2213 push(@{$byaddr{$addr}}, "$len (blkid $blnum)");
  2265         8821  
195             }
196              
197             # Store all the blocks in the freelist(s)
198 58         110 for $len (keys %{$self->{'freelist'}}) {
  58         489  
199 1856         1938 for $addr (@{$self->{'freelist'}->{$len}}) {
  1856         3495  
200 1635         1625 push(@{$byaddr{$addr}}, "$len (free)");
  1635         5470  
201             }
202             }
203              
204 58         223 my($gaps, $overlaps) = (0, 0);
205              
206             # Loop through the blocks in order of address
207 58         716 my(@addrs) = sort {$a <=> $b} keys %byaddr;
  21509         26591  
208 58         266 $addr = 0;
209 58         156 while(@addrs) {
210 3958         5983 my($next) = shift @addrs;
211 3958 50       10282 if ($next > $addr) {
212 0 0       0 print "... ", ($next - $addr), " bytes unaccounted for\n"
213             if $verbose;
214 0         0 $gaps ++;
215             }
216 3958         5297 my(@uses) = @{$byaddr{$next}};
  3958         9206  
217 3958 50       7866 printf "%08x %s\n", $next, join(', ', @uses)
218             if $verbose;
219 3958 50       7236 $overlaps ++ if @uses > 1;
220              
221             # strip off the length (log_2(length) really) from the info str
222 3958         19308 ($len = $uses[0]) =~ s/ .*//;
223 3958         12711 $addr = $next + ( 1 << (0 + $len) );
224             }
225              
226 58 50       2016 ( $gaps == 0 && $overlaps == 0 );
227             }
228              
229             =head2 $allocator->writeMetaData( )
230              
231             Writes the allocator's metadata (header block and root block)
232             back to the file.
233              
234             =cut
235              
236             sub writeMetaData {
237 11     11 1 30 my($self) = @_;
238              
239             # Root block nr is hardcoded to 0.
240             # We don't actually care, but the DSStore btree does.
241 11         23 my($blocknr) = 0;
242              
243             # Before computing the size of the rootblock to allocate it,
244             # make sure it'll be large enough to hold its own (eventual)
245             # allocation information.
246 11 100       58 $self->{offsets}->[0] = undef unless exists $self->{offsets}->[0];
247              
248 11         44 my($rbs) = $self->rootBlockSize();
249 11         49 $self->allocate($rbs, $blocknr);
250            
251 11         37 $self->writeRootblock($self->blockByNumber($blocknr, 1));
252              
253 11         69 my($blockOffset, $blockLength) = $self->blockOffset($blocknr);
254              
255 11         54 $self->{fh}->seek(0, 0);
256 11         5773 $self->{fh}->write(pack('N', 1)); # magic1
257 11         181 $self->_sought(0)->write(pack('a4 NNN a16',
258             'Bud1', # magic
259             $blockOffset, $blockLength, $blockOffset,
260             $self->{unk2}));
261              
262 11         306 $self->{fh}->flush;
263             }
264              
265             sub rootBlockSize {
266 11     11 0 21 my($self) = @_;
267 11         17 my($size);
268              
269 11         18 $size = 8; # The offset count and the unknown field that follows it
270            
271             # The offset blocks, rounded up to a multiple of 256 entries
272 11         26 my($offsetcount) = scalar( @{$self->{'offsets'}} );
  11         38  
273 11         28 my($tail) = $offsetcount % 256;
274 11 100       37 $offsetcount += 256 - $tail if ($tail);
275 11         23 $size += 4 * $offsetcount;
276              
277             # The table of contents
278 11         21 $size += 4; # count
279 11         16 $size += (5 + length($_)) foreach keys %{$self->{'toc'}};
  11         83  
280              
281             # The freelists
282 11         34 foreach my $width (0 .. 31) {
283 352         493 $size += 4 + 4 * scalar( @{$self->{'freelist'}->{$width}} );
  352         724  
284             }
285              
286 11         31 $size;
287             }
288              
289             sub writeRootblock {
290 11     11 0 23 my($self, $into) = @_;
291              
292 11         15 my(@offsets) = @{$self->{'offsets'}};
  11         173  
293            
294             # Write the offset count & the unknown field that follows it
295 11         49 $into->write('NN', scalar(@offsets), $self->{'unk3'});
296            
297             # Write the offsets (using 0 to indicate an unused slot)
298 11 100 66     31 $into->write('N*', map { (defined($_) && $_ > 0)? $_ : 0 } @offsets);
  848         6454  
299            
300             # The offsets are always written in blocks of 256.
301 11         48 my($offsetcount) = scalar(@offsets) % 256;
302 11 100       45 if ($offsetcount > 0) {
303             # Fill out the last block
304 10         76 $into->write('N*', (0) x (256-$offsetcount));
305             }
306              
307             # The DS_Store files only ever have one item in their
308             # table of contents, so I'm not sure if it needs to be sorted or what
309 11         19 my(@tockeys) = sort keys %{$self->{'toc'}};
  11         67  
310 11         38 $into->write('N', scalar(@tockeys));
311 11         28 foreach my $entry (@tockeys) {
312 20         72 $into->write('C a* N', length($entry), $entry, $self->{'toc'}->{$entry});
313             }
314            
315             # And finally the freelists
316 11         43 for my $width ( 0 .. 31 ) {
317 352         861 my($blks) = $self->{'freelist'}->{$width};
318 352         7730 $into->write('N N*', scalar(@$blks), @$blks);
319             }
320             }
321              
322             =head2 $block = $allocator->blockByNumber(blocknumber[, write])
323              
324             Retrieves a block by its block number (I block ID).
325              
326             If C is supplied and is true, then the returned block implements the
327             C method but not the C method.
328              
329             =head2 $block = $allocator->getBlock(offset, size)
330              
331             Retrieves a block (a BuddyAllocator::Block instance) by offset & length.
332             Normally you should use C instead of this method.
333              
334             =cut
335              
336             sub getBlock {
337 13     13 1 32 my($self, $offset, $size) = @_;
338              
339 13         108 return Mac::Finder::DSStore::BuddyAllocator::Block->new($self, $offset, $size);
340             }
341              
342             # Retrieve a block by its block number (small integer)
343             sub blockByNumber {
344 269     269 1 445 my($self, $id, $write) = @_;
345 269         533 my($addr) = $self->{offsets}->[$id];
346 269 50       655 return undef unless $addr;
347 269         352 my($offset, $len);
348 269         384 $offset = $addr & ~0x1F;
349 269         344 $len = 1 << ( $addr & 0x1F );
350             # print " node id $id is $len bytes at 0x".sprintf('%x', $offset)."\n";
351 269 100 66     969 if (!defined($write) || !$write) {
352 193         603 return Mac::Finder::DSStore::BuddyAllocator::Block->new($self, $offset, $len);
353             } else {
354 76         482 return Mac::Finder::DSStore::BuddyAllocator::WriteBlock->new($self, $offset, $len);
355             }
356             }
357              
358             =head2 ( $offset, $size ) = $allocator->blockOffset(blockid)
359              
360             Retrieves the file offset and size in bytes of a given block.
361             The offset doesn't include the 4-byte fudge.
362             In scalar context, just returns the offset.
363              
364             =cut
365              
366             sub blockOffset {
367 204     204 1 1832 my($self, $id) = @_;
368 204         321 my($addr) = $self->{offsets}->[$id];
369 204 100       515 croak "Block $id is not allocated" unless $addr;
370 203         226 my($offset) = $addr & ~0x1F;
371 203 100       490 return $offset unless wantarray;
372 107         278 return ( $offset, 1 << ( $addr & 0x1F ) );
373             }
374              
375             # Return freelist + index of a block's buddy in its freelist (or empty list)
376             sub _buddy {
377 620     620   1009 my($self, $offset, $width) = @_;
378 620         2953 my($freelist, $buddyaddr);
379              
380 620         1247 $freelist = $self->{'freelist'}->{$width};
381 620         850 $buddyaddr = $offset ^ ( 1 << $width );
382              
383 430         1077 return ($freelist,
384 620         1425 grep { $freelist->[$_] == $buddyaddr } 0 .. $#$freelist );
385             }
386              
387             # Free a block, coalescing ith buddies as needed.
388             sub _free {
389 620     620   901 my($self, $offset, $width) = @_;
390              
391 620         1210 my($freelist, $buddyindex) = $self->_buddy($offset, $width);
392              
393 620 100       1476 if(defined($buddyindex)) {
394             # our buddy is free. Coalesce, and add the coalesced block to flist.
395 84         168 my($buddyoffset) = splice(@$freelist, $buddyindex, 1);
396             #&logf("Combining %x with buddy %x", $offset, $buddyoffset);
397 84         246 $self->_free($offset & $buddyoffset, $width+1);
398             } else {
399             #&logf("Adding block %x to freelist %d", $offset, $width);
400 536         2037 @$freelist = sort( @$freelist, $offset );
401             }
402             }
403              
404             # Allocate a block of a specified width, splitting as needed.
405             sub _alloc {
406 803     803   1172 my($self, $width) = @_;
407            
408             #&logf("Allocating a block of width %d", $width);
409             #$loglevel ++;
410              
411 803         1868 my($flist) = $self->{'freelist'}->{$width};
412 803 100       1607 if (@$flist) {
413             # There is a block of the desired size; return it.
414             #&logf("Pulling %x from freelist", $flist->[0]); $loglevel --;
415 365         912 return shift @$flist;
416             } else {
417             # Allocate a block of the next larger size; split it.
418 438         1525 my($offset) = $self->_alloc($width + 1);
419             # and put the other half on the free list.
420 438         976 my($buddy) = $offset ^ ( 1 << $width );
421             #&logf("Splitting %x into %x and %x", $offset, $offset, $buddy);
422             #$loglevel ++;
423 438         1060 $self->_free($buddy, $width);
424             #$loglevel -= 2;
425 438         972 return $offset;
426             }
427             }
428              
429             =head2 $blocknumber = $allocator->allocate($size, [$blocknumber])
430              
431             Allocates or re-allocates a block to be at least C<$size> bytes long.
432             If C<$blocknumber> is given, the specified block will be grown or
433             shrunk if needed, otherwise a new block number will be chosen and
434             given to the allocated block.
435              
436             Unlike the libc C function, this may move a block even if the
437             block is not grown.
438              
439             =head2 $allocator->free($blocknumer)
440              
441             Releases the block number and the block associated with it back to the
442             block pool.
443              
444             =cut
445              
446             sub allocate {
447 369     369 1 5883 my($self, $bytes, $blocknum) = @_;
448 369         681 my($offsets) = $self->{'offsets'};
449              
450             #if(defined($blocknum)) {
451             # &logf("(Re)allocating %d bytes for blkid %d", $bytes, $blocknum);
452             #}
453              
454 369 100       886 if(!defined($blocknum)) {
455 344         429 $blocknum = 1;
456             # search for an empty slot, or extend the array
457 344         28995 $blocknum++ while defined($offsets->[$blocknum]);
458             #&logf("Allocating %d bytes, assigning blkid %d", $bytes, $blocknum);
459             }
460              
461             #$loglevel ++;
462              
463 369         568 my($wantwidth) = 5;
464             # Minimum width, since that's how many low-order bits we steal for the tag
465 369         1294 $wantwidth ++ while $bytes > 1 << $wantwidth;
466              
467 369         424 my($blkaddr, $blkwidth, $blkoffset);
468              
469 369 100 100     1001 if(exists($offsets->[$blocknum]) && $offsets->[$blocknum]) {
470 22         38 $blkaddr = $offsets->[$blocknum];
471 22         34 $blkwidth = $blkaddr & 0x1F;
472 22         30 $blkoffset = $blkaddr & ~0x1F;
473 22 100       57 if ($blkwidth == $wantwidth) {
474             #&logf("Block is already width %d, no change", $wantwidth);
475             #$loglevel --;
476             # The block is currently of the desired size. Leave it alone.
477 8         21 return $blocknum;
478             } else {
479             #&logf("Freeing wrong-sized block");
480             #$loglevel ++;
481             # Free the current block, allocate a new one.
482 14         38 $self->_free($blkoffset, $blkwidth);
483 14         25 delete $offsets->[$blocknum];
484             #$loglevel --;
485             }
486             }
487              
488             # Allocate a block, update the offsets table, and return the new offset
489 361         967 $blkoffset = $self->_alloc($wantwidth);
490 361         558 $blkaddr = $blkoffset | $wantwidth;
491 361         742 $offsets->[$blocknum] = $blkaddr;
492             #$loglevel --;
493 361         954 $blocknum;
494             }
495              
496             sub free {
497 84     84 1 10458 my($self, $blknum) = @_;
498 84         150 my($blkaddr) = $self->{'offsets'}->[$blknum];
499              
500             #&logf("Freeing block index %d", $blknum);
501             #$loglevel ++;
502              
503 84 50       177 if($blkaddr) {
504 84         116 my($blkoffset, $blkwidth);
505 84         112 $blkwidth = $blkaddr & 0x1F;
506 84         88 $blkoffset = $blkaddr & ~0x1F;
507 84         173 $self->_free($blkoffset, $blkwidth);
508             }
509              
510 84         178 delete $self->{'offsets'}->[$blknum];
511             #$loglevel --;
512 84         359 undef;
513             }
514              
515             =head1 ATTRIBUTES
516              
517             =head2 $allocator->{toc}
518              
519             C holds a hashref whose keys are short strings and whose values
520             are integers. This table of contents is read and written as part of the
521             allocator's metadata but is not otherwise used by the allocator;
522             users of the allocator use it to find their data within the file.
523              
524             =head2 $allocator->{fh}
525              
526             The file handle passed in to C or C. If you find yourself needing
527             to use this, you should probably try to extend the class so that you don't.
528              
529             =cut
530              
531             # Used by ...::Block to get a positioned file handle.
532             sub _sought {
533 413     413   671 my($self, $offset) = @_;
534              
535 413         987 my($fh) = $self->{fh};
536 413 50       1887 $fh->seek($offset + $self->{fudge}, 0)
537             or croak;
538 413         13565 $fh;
539             }
540              
541             package Mac::Finder::DSStore::BuddyAllocator::Block;
542              
543             =head1 BuddyAllocator::Block
544              
545             C instances are returned by the
546             C and C methods. They hold a pointer into
547             the file and provide a handful of useful methods.
548              
549             (There are also two other classes, C and C,
550             which might be returned instead. Think of this as an interface rather
551             than as a concrete class.)
552              
553             =head2 $block->read(length, [format])
554              
555             Reads C bytes from the block (advancing the read pointer
556             correspondingly). If C is specified, the bytes read are
557             unpacked using the format; otherwise a byte string is returned.
558              
559             =head2 $block->length( )
560              
561             Returns the length (or size) of this block.
562              
563             =head2 $block->seek(position[, whence])
564              
565             Adjusts the read/write pointer within the block.
566              
567             =head2 $block->write(bytes)
568              
569             =head2 $block->write(format, items...)
570              
571             Writes data to the underlying file, at the position represented by this
572             block. If multiple arguments are given, the first is a format string
573             and the rest are the remaining arguments to C.
574              
575             =head2 $block->close([ zerofill ])
576              
577             This is generally a no-op, but if called on a writable block with
578             C, then zeroes will be written from the current
579             location to the end of the allocated block.
580              
581             =head2 $block->copyback( )
582              
583             Returns the block's contents as a string. For write blocks, this
584             reads from the file. This is just here for debugging purposes and
585             might change.
586              
587             =cut
588              
589 5     5   56 use strict;
  5         11  
  5         183  
590 5     5   31 use warnings;
  5         10  
  5         178  
591 5     5   35 use Carp;
  5         9  
  5         2476  
592              
593             #
594             # Block objects are created by the buddy allocator; they're a
595             # reference to an array with the following components:
596             #
597             # [ $allocator, $value, $position]
598             #
599              
600             sub new {
601 266     266   466 my($class, $allocator, $offset, $size) = @_;
602              
603 266         320 my($value);
604 266 50       645 $allocator->_sought($offset)->read($value, $size)
605             > 0 or die;
606             # Previously, this died if we couldn't read the full block.
607             # Not sure if it's really an error not to read the full
608             # block if the next layer up doesn't need the full block.
609             # So now we're succeeding as long as we get something; if
610             # the reader overruns it'll die in substr().
611              
612 266   33     8775 bless([ $allocator, $value, 0 ], ref $class || $class);
613             }
614              
615             sub read {
616 9240     9240   14494 my($self, $len, $unpack) = @_;
617              
618 9240         12887 my($pos) = $self->[2];
619 9240 50       20277 die "out of range: pos=$pos len=$len max=".(length($self->[1])) if $pos + $len > length($self->[1]);
620 9240         18206 my($bytes) = substr($self->[1], $pos, $len);
621 9240         16302 $self->[2] = $pos + $len;
622            
623 9240 100       37563 $unpack? unpack($unpack, $bytes) : $bytes;
624             }
625              
626             sub length {
627 0     0   0 return CORE::length($_[0]->[1]);
628             }
629              
630             sub close {
631 0     0   0 1;
632             }
633              
634             sub seek {
635 6     6   14 my($self, $pos, $whence) = @_;
636 6 50       24 $whence = 0 unless defined $whence;
637 6 50       16 if ($whence == 0) {
    0          
    0          
638             # pos = pos
639             } elsif ($whence == 1) {
640 0         0 $pos += $self->[2];
641             } elsif ($whence == 2) {
642 0         0 $pos += $self->length();
643             } else {
644 0         0 croak "seek: whence=$whence";
645             }
646 6         17 $self->[2] = $pos;
647             }
648              
649             sub copyback {
650 61     61   235 return $_[0]->[1];
651             }
652              
653             package Mac::Finder::DSStore::BuddyAllocator::WriteBlock;
654              
655 5     5   32 use strict;
  5         9  
  5         139  
656 5     5   28 use warnings;
  5         10  
  5         138  
657 5     5   23 use Carp;
  5         9  
  5         3095  
658              
659             #
660             # Write blocks
661             #
662              
663             sub new {
664 76     76   188 my($class, $allocator, $offset, $size) = @_;
665              
666 76 50 33     361 croak "Missing arguments"
667             unless defined($offset) && defined($size);
668 76 50       218 croak "Bad offset"
669             if $offset <= 0;
670              
671 76   33     900 bless([ $allocator, undef, 0, $offset, $size ], ref $class || $class);
672             }
673              
674             sub read {
675 0     0   0 my($self) = @_;
676              
677 0         0 croak "This is a write-only block";
678             }
679              
680             sub length {
681 0     0   0 return ($_[0]->[4]);
682             }
683              
684             sub seek {
685 0     0   0 my($self, $pos, $whence) = @_;
686 0 0       0 if ($whence == 0) {
    0          
    0          
687 0         0 $self->[2] = $pos;
688             } elsif ($whence == 1) {
689 0         0 $self->[2] += $pos;
690             } elsif ($whence == 2) {
691 0         0 $self->[2] = $self->length + $pos;
692             } else {
693 0         0 croak "seek: whence=$whence";
694             }
695 0         0 undef $self->[1];
696 0         0 $self;
697             }
698              
699             sub write {
700 2459     2459   29863 my($self, $what, @args) = @_;;
701              
702 2459 100       5953 if (!defined($self->[1])) {
703 136         633 $self->[1] = $self->[0]->_sought($self->[2] + $self->[3]);
704             }
705              
706 2459 100       5412 if (@args) {
707 1777         5170 $what = pack($what, @args);
708             }
709              
710 2459         4471 my($wlen) = CORE::length($what);
711              
712 2459 50       8853 croak "Writing past end of block (writing $wlen at ".($self->[2]).", end is at ".($self->[4])."), died"
713             if $self->[2]+$wlen > $self->[4];
714              
715 2459         8364 $self->[1]->write($what);
716 2459         92584 $self->[2] += $wlen;
717             }
718              
719             sub close {
720 65     65   256 my($self, $fill) = @_;
721 65 50 66     751 if (defined($fill) && $fill && $self->[2] < $self->[4]) {
      66        
722 60         456 $self->write("\0" x ($self->[4] - $self->[2]));
723             }
724 65         161 undef $self->[1];
725 65         152 1;
726             }
727              
728             #
729             # This is just here for debugging/testing purposes
730             #
731              
732             sub copyback {
733 60     60   139 my($self) = @_;
734              
735 60         150 my($r) = Mac::Finder::DSStore::BuddyAllocator::Block->new(@{$self}[0, 3, 2]);
  60         420  
736              
737 60         147 undef $self->[1]; # probably need to re-seek now
738              
739 60         337 return $r;
740             }
741              
742             package Mac::Finder::DSStore::BuddyAllocator::StringBlock;
743              
744 5     5   50 use strict;
  5         11  
  5         180  
745 5     5   34 use warnings;
  5         7  
  5         867  
746              
747             #
748             # This one's kind of handy, really, but is only used for debugging and
749             # test harnesses right now.
750             #
751              
752             sub new {
753 61     61   104 my($x) = '';
754 61   33     418 bless(\$x, ref $_[0] || $_[0]);
755             }
756              
757             sub write {
758 2005     2005   19990 my($self, $what, @args) = @_;;
759              
760 2005 100       4348 if (@args) {
761 1376         4156 $what = pack($what, @args);
762             }
763              
764 2005         2490 ${$self} .= $what;
  2005         8207  
765             }
766              
767             sub copyback {
768 61     61   78 ${$_[0]};
  61         288  
769             }
770              
771             =head1 AUTHOR
772              
773             Written by Wim Lewis as part of the Mac::Finder::DSStore package.
774              
775             This file is copyright 2008 by Wim Lewis.
776             All rights reserved.
777             This program is free software; you can redistribute it and/or
778             modify it under the same terms as Perl itself.
779              
780              
781             =cut
782              
783             1;