File Coverage

blib/lib/FileSystem/LL/FAT.pm
Criterion Covered Total %
statement 3 420 0.7
branch 0 294 0.0
condition 0 181 0.0
subroutine 1 34 2.9
pod 10 26 38.4
total 14 955 1.4


line stmt bran cond sub pod time code
1             package FileSystem::LL::FAT;
2              
3             #use 5.008008;
4             #use warnings;
5              
6             require Exporter;
7              
8             @ISA = qw(Exporter);
9              
10             # Items to export into callers namespace by default. Note: do not export
11             # names by default without a very good reason. Use EXPORT_OK instead.
12             # Do not simply export all your public functions/methods/constants.
13              
14             # This allows declaration use FileSystem::LL::FAT ':all';
15             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
16             # will save memory.
17             %EXPORT_TAGS = ( 'all' => [ qw(
18             MBR_2_partitions debug_partitions emit_fat32 interpret_directory
19             check_bootsector interpret_bootsector
20             check_FAT_array FAT_2array cluster_chain read_FAT_data
21             write_file write_dir list_dir compress_FAT uncompress_FAT
22             ) ] );
23              
24             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
25              
26             @EXPORT = qw(
27            
28             );
29              
30             $VERSION = '0.05';
31 1     1   27878 use strict;
  1         4  
  1         12191  
32              
33             my $lim_read = $ENV{FAT_READ_NEEDS_1SECTOR};
34             $lim_read = ($^O eq 'os2') unless defined $lim_read; #Bug in OS/2 FAT32 driver?
35             $lim_read = $lim_read ? 512 : (1<<24);
36              
37             # Preloaded methods go here.
38             sub decode_fields ($$) {
39 0     0 0   my ($fields2, $in) = (shift,shift);
40 0           my $lastfield = @$fields2/2 - 1;
41 0           my $extract = join ' ', @$fields2[map 2*$_ + 1, 0 .. $lastfield];
42 0           my @values = unpack $extract, $in;
43 0           map( ($$fields2[2*$_] => $values[$_]), 0 .. $lastfield);
44             }
45              
46              
47              
48             sub MBR_2_partitions ($) {
49 0     0 1   my $bootsect = shift;
50             return # die "Expect to have \\x55\\xAA in MBR"
51 0 0 0       unless length($bootsect) == 512 and "\x55\xAA" eq substr $bootsect, -2;
52             # Up to offset 1BEh the MBR consists purely of machine code and data (strings
53             # etc.). At offset 1BEh the first primary partition is defined, this takes 16
54             # bytes, after which the second primary partition is defined, followed by
55             # the third and fourth, the data structures are the same.
56              
57 0           my ($code, @parts) = unpack 'a446 a16 a16 a16 a16 v', $bootsect;
58 0           my $check = pop @parts;
59              
60             # 00h 1 Set to 80h if this partition is active.
61             # 01h 1 Partition's starting head.
62             # 02h 2 Partition's starting [48]sector and track.
63             # 04h 1 Partition's [49]ID number.
64             # 05h 1 Partition's ending head.
65             # 06h 2 Partition's ending [50]sector and track.
66             # 08h 4 Starting LBA.
67             # 0Ch 4 Partition's length in sectors.
68              
69             # Format of sector and track information.Bits 15-6 Bits 5-0
70             # Track Sector
71             # ID numbers:
72             # 0Bh Win95 OSR2+ FAT32 (512MB-2TB) (primary?)
73             # 0Ch Win95 OSR2+ FAT32 (512MB-2TB LBA) (extended?)
74 0           my @part = ( # code => 'A446',
75             is_active => 'C',
76             start_head => 'C',
77             start_sec_track => 'v',
78             type => 'C',
79             end_head => 'C',
80             end_sec_track => 'v',
81             start_lba => 'V',
82             sectors => 'V',
83             );
84              
85 0           my @part_value = map { {raw => $_, decode_fields \@part, $_} } @parts;
  0            
86 0           for my $p (@part_value) {
87 0           $p->{start_sec} = $p->{start_sec_track} & 0x3f;
88 0           $p->{start_track} = $p->{start_sec_track} >> 6;
89 0           $p->{end_sec} = $p->{end_sec_track} & 0x3f;
90 0           $p->{end_track} = $p->{end_sec_track} >> 6;
91             }
92 0           ({bootcode => $code, signature => $check}, @part_value);
93             }
94              
95             sub debug_partitions ($@) {
96 0     0 0   my($fh, @partitions) = @_;
97 0           my $n;
98 0           for my $p (@partitions) {
99 0           $n++;
100 0           print $fh " Part $n\n";
101 0           print $fh "$_\t=> $p->{$_}\n" for sort keys %$p;
102             }
103             }
104              
105             # Experimental convertor from empty fat to empty fat32...
106             sub emit_fat32 ($$$$) { # Also, essentially, seeks to bootsector
107 0     0 0   my($p, $b, $emit_prefat32, $reader) = (shift, shift, shift, shift);
108              
109 0           my $offset_in_sectors = $p->{start_lba};
110 0 0         die "The partition type is not defined" unless $p->{type};
111 0 0         die "start_lba value is 0" unless $offset_in_sectors;
112              
113 0           substr($b, 446 + 4, 1) = chr 0x0B; # Win95 OSR2+ FAT32 (512MB-2TB) (primary?)
114 0 0         die "Need emit_prefat32 defined too in presence of partition table"
115             unless defined $emit_prefat32;
116 0 0         open F32, "> $emit_prefat32" or die "Error opening `$emit_prefat32' for write: $!";
117 0           binmode F32;
118 0           syswrite F32, $b;
119 0 0         if ($offset_in_sectors > 1) {
120 0           my $in = $reader->(512*($offset_in_sectors - 1));
121 0           syswrite F32, $in;
122             }
123 0 0         close F32 or die "Error closing `$emit_prefat32' for write: $!";
124             }
125              
126             # Directory Entry Layout.
127             #
128             # The old style directory entry had 10 reserved bytes starting at 0Ch,
129             # these are now used.
130             # 00h 8 Filename padded with spaces if required (see above).
131             # 08h 3 Filename extension padded with spaces if required.
132             # 0Bh 1 File Attribute Byte.
133             # 0Ch 10 Reserved or extra data.
134             # 16h 2 Time of last write to file (last modified or when created).
135             # 18h 2 Date of last write to file (last modified or when created).
136             # 1Ah 2 Starting cluster.
137             # 1Ch 4 File size (set to zero if a directory).
138             #
139             #
140             # Extra data Layout (previously reserved area).
141             #
142             # The old style directory entry had 10 reserved bytes starting at 0Ch,
143             # these are now used as follows. Presumably these fields are used if
144             # non-zero.
145             # Offset Length Field
146             # 0Ch 1 Reserved for use by Windows NT.
147             # 0Dh 1 Tenths of a second at time of file creation, 0-199 is valid.
148             # 0Eh 2 Time when file was created.
149             # 10h 2 Date when file was created.
150             # 12h 2 Date when file was last accessed.
151             # 14h 2 High word of cluster number (always 0 for FAT12 and FAT16).
152              
153             my @file_f = ( basename => 'A8',
154             ext => 'A3',
155             attrib => 'C',
156             name_ext_case => 'C',
157             creation_01sec => 'C',
158             time_creation => 'v',
159             date_create => 'v',
160             date_access => 'v',
161             cluster_high => 'v',
162             time_write => 'v',
163             date_write => 'v',
164             cluster_low => 'v',
165             size => 'V',
166             );
167              
168             my @lfn_f = ( seq_number => 'C',
169             name_chars_1 => 'a10',
170             attrib => 'C',
171             nt_reserved => 'C',
172             checksum_dosname => 'C',
173             name_chars_2 => 'a12',
174             cluster_low => 'v',
175             name_chars_3 => 'a4',
176             );
177              
178             my $nn = 0;
179             my %file_attrib = map +($_ => 1<<($nn++)),
180             qw(is_readonly is_hidden is_system is_volume_label
181             is_subdir is_archive is_device);
182              
183             sub dos_chksum ($$) {
184 0     0 0   my ($n,$ext,$sum) = (shift, shift, 0);
185             $sum = ((($sum & 1)<<7) + ($sum >> 1) + ord $_) & 0xFF
186 0           for split //, sprintf "%-8s%-3s", $n, $ext;
187 0           $sum
188             }
189              
190             sub interpret_directory ($$;$$$) {
191 0     0 1   my ($dir, $is_fat32, $keep_del, $keep_dots, $keep_labels) =
192             (shift, shift, shift, shift, shift);
193 0           my ($res, @files, @lfn, $lfn_checksum, $lfn_seq, $lfn_tot, $lfn_del);
194 0           while (length $dir) {
195 0 0         $dir =~ s/^((.).{31})//s or die "short directory!";
196 0 0         $res = 'end', last if $2 eq "\0"; # No entries after this point
197 0 0 0       next if not $keep_del and 0xE5 == ord $2; # deleted or not filled
198 0           my %f = decode_fields \@file_f, $1;
199 0           $f{deleted} = 0;
200 0 0         $f{deleted} = 1 if 0xE5 == ord $2; # deleted or not filled
201 0 0         if ($f{attrib} == 0x0F) { # LFN
202             # next;
203 0           %f = decode_fields \@lfn_f, $1;
204 0 0         if (not $keep_del) { # XXX How to process? Ignore seq numbers???
205 0 0         @lfn = (), next if $f{seq_number} & 0x80; # Deleted entry
206             } else { # Deleted entry for non-deleted file?
207 0 0 0       @lfn = (), next if $f{seq_number} & 0x80 and $f{seq_number} != 0xE5;
208 0 0         if ($f{seq_number} == 0xE5) {
209 0 0 0       die "Deleted LFN subrecord in middle of LFN" if @lfn and !$lfn_del;
210 0           $lfn_del = 1;
211 0           $f{deleted} = 1;
212             } else { # Ignore deleted LFN preceeding a valid LFN
213 0 0 0       @lfn = () if @lfn and $lfn_del;
214 0           $lfn_del = 0;
215             }
216             }
217 0           $f{raw} = $1;
218 0 0         unless ($f{deleted}) {
219 0 0 0       die "LFN start unexpected" if @lfn and $f{seq_number} & 0x40;
220 0 0 0       die "LFN continuation unexpected" unless @lfn or $f{seq_number} & 0x40;
221              
222 0 0 0       die "LFN continuation out-of-order: $f{seq_number} after $lfn_seq"
223             if @lfn and $f{seq_number} != ($lfn_seq & ~0x40) - 1;
224 0           $lfn_seq = $f{seq_number};
225              
226 0 0 0       die "Mismatch in checksums"
227             if @lfn and $lfn_checksum != $f{checksum_dosname};
228             }
229 0           $lfn_checksum = $f{checksum_dosname};
230 0           $f{lfn_chars} = "$f{name_chars_1}$f{name_chars_2}$f{name_chars_3}";
231 0 0         $f{lfn_chars} =~ s/\0\0(\xFF\xFF){0,11}$// # may be non-terminated...
232             # or die "LFN `$f{lfn_chars}' not terminated by 0x0000"
233             unless @lfn;
234 0 0         if (@lfn) {
235 0           $lfn_tot = "$f{lfn_chars}$lfn_tot"
236             } else {
237 0           $lfn_tot = $f{lfn_chars}
238             }
239 0           push @lfn, \%f;
240 0           next;
241             }
242 0           $f{raw} = $1;
243 0           $f{basename} =~ s/^\x05/\xE5/;
244 0 0 0       @lfn = (), next
      0        
245             if not $keep_dots and $f{basename} =~ /^\.\.?$/ and $f{ext} eq ''; # . ..
246             # DOSname is mangled for deleted files, so there is no point in checksum...
247 0 0 0       @lfn = (), warn("Mis-attached LFN (chksum mismatch: $lfn_checksum vs `$f{basename}.$f{ext}')")
      0        
248             if @lfn and not $lfn_del and $lfn_checksum != dos_chksum($f{basename}, $f{ext});
249 0 0 0       next if ($f{attrib} & 0x08) and not $keep_labels;
250 0 0         if ($is_fat32) {
251 0           $f{cluster} = $f{cluster_low} + ($f{cluster_high} << 16);
252             } else {
253 0           $f{cluster} = $f{cluster_low}; # cluster_high has EA info?
254             }
255 0 0         $f{basename} = lc $f{basename} if $f{name_ext_case} & (1<<3);
256 0 0         $f{ext} = lc $f{ext} if $f{name_ext_case} & (1<<4);
257 0 0         my $ext = length $f{ext} ? ".$f{ext}" : '';
258 0           $f{dos_name} = $f{name} = "$f{basename}$ext";
259 0           $f{time_create} = $f{time_creation} + $f{creation_01sec}/100;
260 0           $f{$_} = $f{attrib} & $file_attrib{$_} for keys %file_attrib;
261 0 0         if (@lfn) {
262 0           $f{lfn_raw} = [@lfn];
263 0           $f{name} = join '', map chr, unpack 'v*', $lfn_tot;
264 0           $f{lfn_name_UTF16} = $lfn_tot;
265             }
266 0           push @files, \%f;
267 0           @lfn = ();
268             }
269 0 0 0       $res ||= 'mid' if @lfn;
270 0           ($res, \@files);
271             }
272              
273             # FAT12/FAT16 Boot Sector/Boot Record Layout.
274              
275             # The data contained in the boot sector after the OEM name string is
276             # referred to as the BIOS parameter block or BPB.
277             # Offset Length Field
278             # 00h 3 Machine code for jump over the data.
279             # 03h 8 OEM name string (of OS which formatted the disk).
280             # 0Bh 2 Bytes per sector, nearly always 512 but can be 1024,2048 or
281             # 4096.
282             # 0Dh 1 Sectors per cluster, valid number are: 1,2,4,8,16,32,64 and 128,
283             # but a cluster size larger than 32K should not occur.
284             # 0Eh 2 Reserved sectors (number of sectors before the first FAT
285             # including the boot sector), usually 1.
286             # 10h 1 Number of FAT's (nearly always 2).
287             # 11h 2 Maximum number of root directory entries.
288             # 13h 2 Total number of sectors (for small disks only, if the disk is
289             # too big this is set to 0 and offset 20h is used instead).
290             # 15h 1 Media descriptor byte, pretty meaningless now (see below).
291             # 16h 2 Sectors per FAT.
292             # 18h 2 Sectors per track.
293             # 1Ah 2 Total number of heads/sides.
294             # 1Ch 4 Number of hidden sectors (those preceding the boot sector).
295             # 20h 4 Total number of sectors for large disks.
296             # Starts FAT12/16-specific
297             # 24h 26 Either extended BPB (see below) or machine code.
298             # 3Eh 448 Machine code.
299             # 1FEh 2 Boot Signature AA55h.
300              
301             # Starts FAT32-specific
302             # 0x24 4 Sectors per file allocation table
303             # 0x28 2 FAT Flags
304             # 0x2a 2 Version
305             # 0x2c 4 Cluster number of root directory start
306             # 0x30 2 Sector number of FS Information Sector
307             # 0x32 2 Sector number of a copy of this boot sector
308             # 0x34 12 Reserved
309             # 0x40 1 Physical Drive Number
310             # 0x41 1 Reserved
311             # 0x42 1 Extended boot signature.
312             # 0x43 4 ID (serial number)
313             # 0x47 11 Volume Label
314             # 0x52 8 FAT file system type: "FAT32 "
315             # 0x5a 420 Operating system boot code
316             # 0x1FE 2 Boot sector signature (0x55 0xAA)
317              
318              
319             my @boot_c = ( jump => 'A3',
320             oem => 'A8',
321             sector_size => 'v',
322             sectors_in_cluster => 'C',
323             FAT_table_off => 'v',
324             num_FAT_tables => 'C',
325             root_dir_entries => 'v',
326             total_sectors1 => 'v',
327             media_type => 'C',
328             sectors_per_FAT16 => 'v',
329             sectors_per_track => 'v',
330             heads => 'v',
331             hidden_sectors => 'V',
332             total_sectors2 => 'V',
333             );
334             my @boot_16 = ( extended_bpb => 'a26',
335             machine_code => 'a448',
336             boot_signature => 'v',
337             );
338             my @boot_32 = ( sectors_per_FAT32 => 'V',
339             FAT_flags => 'v',
340             version => 'v',
341             rootdir_start_cluster => 'V',
342             fsi_sector_sector => 'v',
343             bootcopy_sector_sector => 'v',
344             reserved1 => 'a12',
345             physical_drive => 'C',
346             reserved2 => 'C',
347             ext_boot_signature => 'C',
348             serial_number => 'V',
349             volume_label => 'A11',
350             FS_type => 'A8',
351             machine_code => 'a420',
352             boot_signature => 'v',
353             );
354              
355              
356             # FAT12/Fat16 Extended BPB.
357             #
358             # The Extended BIOS parameter block is not present prior to DOS 4.0
359             # formatted disks.
360             # Offset
361             # Length (in bytes)
362             # Field
363             # 24h 1 Physical drive number (BIOS system ie 80h is first HDD, 00h is first FDD).
364             # 25h 1 Current head (not used for this; WinNT bit 0 is a dirty flag to request chkdsk at boot time. bit 1 requests surface scan too).
365             # 26h 1 Signature (must be 28h or 29h to be recognised by NT).
366             # 27h 4 The serial number, the serial number is stored in reverse order
367             # and is the hex representation of the bytes stored here.
368             # 2Bh 11 Volume label.
369             # 36h 8 File system ID. "FAT12", "FAT16" or "FAT ".
370              
371             # Further structure used by FAT32:
372             # Byte Offset Length (bytes) Description
373             # 0x24 4 Sectors per file allocation table
374             # 0x28 2 FAT Flags
375             # 0x2a 2 Version
376             # 0x2c 4 Cluster number of root directory start
377             # 0x30 2 Sector number of FS Information Sector
378             # 0x32 2 Sector number of a copy of this boot sector
379             # 0x34 12 Reserved
380             # 0x40 1 Physical Drive Number
381             # 0x41 1 Reserved
382             # 0x42 1 Extended boot signature (0x28 0x29).
383             # 0x43 4 ID (serial number)
384             # 0x47 11 Volume Label
385             # 0x52 8 FAT file system type: "FAT32 "
386             # 0x5a 420 Operating system boot code
387             # 0x1FE 2 Boot sector signature (0x55 0xAA)
388              
389             my @e_boot = (
390             physical_drive => 'C',
391             head___dirty_flags => 'C',
392             ext_boot_signature => 'C',
393             serial_number => 'V',
394             volume_label => 'A11',
395             FS_type => 'A8',
396             );
397              
398             # FS Information Sector
399             # Byte Offset Length (bytes) Description
400             # 0x00 4 FS information sector signature (0x52 0x52 0x61 0x41 / "RRaA")
401             # 0x04 480 Reserved (byte values are 0x00)
402             # 0x1e4 4 FS information sector signature (0x72 0x72 0x41 0x61 / "rrAa")
403             # 0x1e8 4 Number of free clusters on the drive, or -1 if unknown
404             # 0x1ec 4 Number of the most recently allocated cluster
405             # 0x1f0 14 Reserved (byte values are 0x00)
406             # 0x1fe 2 FS information sector signature (0x55 0xAA)
407              
408             sub preprocess_bootsect ($) {
409 0     0 0   my $s = shift;
410 0   0       $s->{total_sectors} = $s->{total_sectors1} || $s->{total_sectors2};
411 0   0       $s->{sectors_per_FAT} = $s->{sectors_per_FAT32} || $s->{sectors_per_FAT16};
412 0           $s->{pre_sectors} = $s->{FAT_table_off}
413             + $s->{num_FAT_tables} * $s->{sectors_per_FAT}
414             + $s->{root_dir_entries} * 0x20 / $s->{sector_size};
415 0           $s->{sector_of_cluster0} = $s->{pre_sectors} - 2*$s->{sectors_in_cluster};
416 0           $s->{last_cluster} = int(($s->{total_sectors} - $s->{pre_sectors}
417             + $s->{sectors_in_cluster} - 1)/$s->{sectors_in_cluster}) + 2;
418             }
419              
420             sub guess_width ($$) {
421 0     0 0   my ($s, $raw) = (shift, shift);
422 0           my $w = 12;
423 0           my %bpb = decode_fields \@e_boot, $s->{extended_bpb};
424 0 0 0       if ($s->{last_cluster} >= 0x10000 or $s->{root_dir_entries} == 0
    0 0        
      0        
425             or $bpb{ext_boot_signature} != 0x28 and $bpb{ext_boot_signature} != 0x29) {
426 0           $w = 32;
427 0           %$s = decode_fields [@boot_c, @boot_32], $raw;
428 0           preprocess_bootsect $s;
429             } elsif ($s->{last_cluster} >= 0x1000) {
430 0           $w = 16
431             } else { # Any other way to determine width???
432             }
433 0           $s->{bpb_ext_boot_signature} = $bpb{ext_boot_signature};
434 0 0         @$s{keys %bpb} = values %bpb unless $w == 32;
435 0           $s->{guessed_FAT_flavor} = $w;
436 0           $s->{raw} = $raw;
437             }
438              
439             sub interpret_bootsector ($) {
440 0     0 1   my $bootsect = shift;
441 0           my $s = {decode_fields [@boot_c, @boot_16], $bootsect};
442 0           preprocess_bootsect $s;
443 0           guess_width $s, $bootsect;
444 0           $s
445             }
446              
447             sub check_bootsector ($;$) {
448 0     0 1   my $s = shift;
449             # Expected size of FAT with 12bit per entry
450 0           my $exp = $s->{last_cluster} * $s->{guessed_FAT_flavor}/8/$s->{sector_size};
451 0 0         die "FAT has $s->{sectors_per_FAT} sectors: expecting $exp"
452             unless $s->{sectors_per_FAT} >= $exp;
453 0 0         warn "FAT has $s->{sectors_per_FAT} sectors: expecting $exp"
454             unless $s->{sectors_per_FAT} <= $exp + 10;
455              
456             # How to distinguish bootsector from MBR? Jump on FAT12 is "EB 3C 90";
457             # 0x90 is NOP, 0xEB is jump(displacement8). In FAT32, there are extra
458             # 28 bytes, so displacement should be 0x58. To be extra safe (e.g., allow
459             # chymera bootsector-and-MBR), one should tolerate other jumps...
460              
461 0 0 0       die sprintf "Unexpected bootsector: first byte %#02x\n",
462             ord substr $s->{raw},0,1
463             if shift and not $s->{raw} =~ /^\xEB/; # Check JMP instruction
464 0 0 0       return 1 if
      0        
      0        
      0        
      0        
465             ($s->{ext_boot_signature} == 0x28 or $s->{ext_boot_signature} == 0x29)
466             and $s->{boot_signature} == 0xAA55
467             and $s->{FS_type} =~ /^fat(\d{2})?/i
468             and (not $1 or $1 eq $s->{guessed_FAT_flavor});
469 0           die <
470             Unexpected bootsector: guessed_width=$s->{guessed_FAT_flavor}, last_cluster=$s->{last_cluster}, root_dir_entries=$s->{root_dir_entries},
471             boot_signature=$s->{boot_signature}, ext_boot_signature16=$s->{bpb_ext_boot_signature}, ext_boot_signature=$s->{ext_boot_signature}, FS_type=$s->{FS_type}
472             EOD
473             }
474              
475             sub string_to_n ($$$$) {
476 0     0 0   my($s, $offset, $n, $w) = (shift, shift, shift, shift);
477 0           my($n2, $w2) = ($n, ($w>>3));
478 0 0         $n2 >>= 1, $w2 = 3 if $w == 12;
479 0           $offset += $w2 * $n2;
480 0           my $out = unpack 'V', substr($$s, $offset, $w2) . "\0\0";
481 0 0         if ($w == 12) {
482 0 0         if ($n % 2) {
483 0           $out >>= 12
484             } else {
485 0           $out &= 0xFFF
486             }
487             }
488             $out
489 0           }
490              
491             sub FAT_2array ($$$;$$) {
492 0   0 0 1   my($fat, $s, $w, $offset, $lim) = (shift, shift, shift, shift || 0, shift);
493 0 0         $lim = length($$s) - $offset unless defined $lim;
494 0 0         die "Too large offset=$offset, lim=$lim" if $lim + $offset > length $$s;
495 0 0         if ($w == 12) {
496 0           $lim += $offset;
497 0           while ($offset < $lim) {
498 0           my $ss = substr $$s, $offset, 3;
499 0           my $n32 = unpack 'V', "$ss\0";
500             # warn sprintf "got %#04x\n", $n32;
501 0           push @$fat, ($n32 & 0xFFF), ($n32 >> 12);
502 0           $offset += 3;
503             }
504             } else {
505 0 0         my $f = ($w == 32) ? 'V' : 'v';
506 0           $w >>= 3;
507 0           $lim = int($lim/$w);
508             # Do not extend stack too much:
509 0           while ($lim >= 1) {
510 0 0         my $l = ($lim > 1000) ? 1000 : $lim;
511 0           push @$fat, unpack "x$offset $f$l", $$s;
512 0           $lim -= $l;
513 0           $offset += $l * $w;
514             }
515             }
516             # warn "FAT = @$fat\n"
517             }
518              
519             sub check_FAT_array ($$;$) {
520 0   0 0 1   my ($fat, $b, $offset, @fat) = (shift, shift, shift || 0);
521 0 0         FAT_2array(\@fat, $fat, $b->{guessed_FAT_flavor}, $offset, 2*4),
522             $fat = \@fat unless 'ARRAY' eq ref $fat; # Make into array
523              
524 0           my $max_cluster = (1<<$b->{guessed_FAT_flavor}) - 1;
525 0 0         die sprintf "Wrong signature %d=%#x, media=%#x in cluster(0)",
526             $fat->[0], $fat->[0], $b->{media_type}
527             unless $fat->[0] == (($b->{media_type} | 0xffffff00) & $max_cluster);
528              
529 0           my $eof = $fat->[1]; # Leading 0 in FAT32:
530 0 0         die sprintf "Wrong signature %d=%#x in cluster(1)", $eof, $eof
531             unless ($eof >> 3) == ($max_cluster >> (3 + 4*(32==$b->{guessed_FAT_flavor})));
532 0           return 1;
533             }
534              
535             sub cluster_chain ($$$$;$$) {
536 0   0 0 1   my ($cluster, $maxc, $fat, $b, $compress, $offset) = (shift, shift, shift, shift, shift, shift||0);
537 0           my $last_cluster = $b->{last_cluster};
538 0 0 0       die "problem with cluster=$cluster as a cluster leader"
539             unless $cluster >= 2 and $cluster <= $last_cluster;
540 0           my ($c, @clusters) = (1, $cluster);
541 0           my $w = $b->{guessed_FAT_flavor};
542 0           my $stop_3 = (1<<($w - 3 - 4*($w==32))) - 1; # Leading 0 in FAT32
543 0           my $total = 1;
544 0   0       my $subr = ($compress and ref $compress eq 'CODE' and $compress);
545 0           while (--$maxc) {
546             # warn "processing $cluster, rem=$maxc, stop_3=$stop_3, w=$w";
547 0           my $next;
548 0 0         if (ref $fat eq 'ARRAY') {
549 0           $next = $fat->[$cluster];
550             } else { # A reference to 'V*'-string
551 0           $next = string_to_n($fat, $offset, $cluster, $w);
552             }
553 0 0         if ($compress) {
554 0 0         $c++, next if $next == ++$cluster;
555 0 0         if ($subr) {
556 0           $subr->($clusters[-1], $c);
557 0           pop @clusters;
558             } else {
559 0           push @clusters, $c;
560             } # New cluster would be inserted later
561 0           $total += $c - 1;
562 0           $c = 1;
563             }
564 0 0         return $total, \@clusters if ($next >> 3) == $stop_3;
565 0 0         $next = 'undef' unless defined $next; # XXX ???
566 0 0 0       die "problem with cluster(+1)=$cluster => $next in a cluster chain"
567             unless $next >= 2 and $next <= $last_cluster;
568 0           $total++, push @clusters, $cluster = $next;
569             }
570 0           return 0, \@clusters
571             }
572              
573 0 0   0 0   sub min($$){my($a,$b)=@_;$a>$b? $b:$a}
  0            
574              
575             sub seek_and_read ($$$$;$) {
576 0     0 0   my ($fh, $seek, $read) = (shift,shift,shift);
577 0 0 0       sysseek $fh, $seek, 0 or die "sysseek $seek: $!" if defined $seek;
578 0 0         $_[0]=' ', $_[0] x= $read, $_[0] = '' unless defined $_[0];
579 0 0 0       die "seek_and_read outside of string" if ($_[1] || 0) > length $_[0];
580 0   0       substr($_[0], $_[1] || 0) = '';
581 0           my($r,$t,$c) = ($read, 0);
582 0   0       $r -= $c, $t += $c
583             while $r and $c = sysread $fh, $_[0], min($r, $lim_read), length $_[0];
584 0 0         die "Short read ($t instead of $read)" unless $t == $read;
585 0           1;
586             }
587              
588             sub read_FAT_data ($$;$$$) {
589 0   0 0 1   my ($fh, $how, $offset, $b, $FAT) = (shift, shift, shift||0, shift, shift);
590 0           my ($close, $inif, $out, $mbr, $b_read);
591 0 0         unless (ref $fh) {
592 0 0         open IN, '<', $inif = $fh or die "open `$fh' for read: $!";
593 0           $fh = \*IN;
594 0           $close = 1;
595             }
596 0           binmode $fh;
597 0 0         if (defined $how->{do_MBR}) {
598 0           seek_and_read $fh, $offset, 512, $mbr;
599 0 0 0       if ($how->{do_MBR} eq 'maybe' and defined $how->{do_bootsector}) {
600 0 0         eval { my $b1 = interpret_bootsector $mbr;
  0            
601 0           check_bootsector $b1;
602 0           $out->{bootsect_off} = $offset;
603 0           $b = $out->{bootsector} = $b1;
604 0           $b_read = 1 } and undef $mbr;
605             }
606 0 0 0       if ($mbr and (defined $how->{parse_MBR} or defined $how->{do_bootsector}
      0        
607             or defined $how->{do_rootdir} or defined $how->{do_FAT})) {
608 0 0         my($fields, @p) = MBR_2_partitions $mbr or die "Wrong signature in MBR";
609 0 0         my @valid = defined $how->{partition} ? $how->{partition} : (0..3);
610             # Type = 0 is Empty; FreeSpace is not marked as a partition???
611 0   0       @valid = grep $p[$_]{start_lba} && $p[$_]{sectors} && $p[$_]{type}, @valid;
612 0 0         unless (@valid) {
613 0 0         die "Partition $how->{partition} invalid" if $how->{partition};
614 0           die "No valid partition found";
615             }
616 0 0         die "Too many valid partitions: @valid" if @valid > 1;
617 0           $offset += $p[$valid[0]]{start_lba} * 512;
618 0           $out->{mbr} = {%$fields, partitions => \@p};
619             }
620             }
621 0 0 0       if (defined $how->{do_bootsector} and not $b_read) {
622 0 0         die "Bootsector given as argument and needs to be read too?" if $b;
623 0           seek_and_read $fh, $offset, 512, my $bs;
624 0 0 0       if (defined $how->{parse_bootsector} or defined $how->{do_rootdir}
      0        
625             or defined $how->{do_FAT}) {
626 0           $b = interpret_bootsector $bs;
627 0           check_bootsector $b;
628             } else {
629 0           $b = {raw => $bs};
630             }
631 0           $out->{bootsector_offset} = $offset;
632 0           $out->{bootsector} = $b;
633             }
634 0 0         if (defined $how->{do_FAT}) {
635 0 0         die "need bootsector" unless $b;
636 0 0         die "FAT given as argument and needs to be read too?" if $FAT;
637 0           my $o = $offset;
638 0 0         $o += ($b->{FAT_table_off} + $how->{do_FAT} * $b->{sectors_per_FAT})
639             * $b->{sector_size} unless $how->{FAT_separate};
640 0 0         die "FAT[$how->{do_FAT}] not present: only $b->{num_FAT_tables} FAT table"
641             if $b->{num_FAT_tables} <= $how->{do_FAT};
642 0           seek_and_read $fh, $o, $b->{sector_size} * $b->{sectors_per_FAT}, my $F;
643 0 0 0       if (defined $how->{parse_FAT}
      0        
644             and $b->{last_cluster} < ($how->{parse_FAT} || 3e6)) {
645 0           my @f;
646 0           $#f = $b->{last_cluster};
647 0           @f = ();
648 0           FAT_2array(\@f, \$F, $b->{guessed_FAT_flavor});
649 0           $FAT = \@f;
650             } else {
651 0           $FAT = \$F;
652             }
653 0           $out->{FAT} = $FAT;
654 0 0 0       $out->{FAT_raw} = \$F if $how->{raw_FAT} or not defined $how->{parse_FAT};
655             }
656 0 0         if (defined $how->{do_rootdir}) {
657 0 0         die "need bootsector" unless $b;
658 0           my($s, $l, $o) = '';
659 0 0         if ($how->{rootdir_is_standalone}) {
660 0           local $/;
661 0           $s = <$fh>;
662             } else {
663 0           my($L, $S) = ($b->{sector_size} * $b->{sectors_in_cluster},
664             $offset + $b->{sector_of_cluster0}*$b->{sector_size});
665 0 0         if ($b->{guessed_FAT_flavor} == 32) {
666             my $appender = sub ($$) {
667 0     0     my($start, $len) = (shift, shift);
668 0           seek_and_read $fh, $S + $L * $start, $len * $L, $s, length $s;
669             }
670 0           ;
671 0 0         if ($FAT) {
672 0           cluster_chain($b->{rootdir_start_cluster}, 0, $FAT, $b, $appender);
673             } else {
674 0           $appender->($b->{rootdir_start_cluster}, 1); # XXX Assume 1 cluster
675             }
676             } else {
677 0           my $off = ($offset + $b->{sector_size} *
678             ($b->{FAT_table_off} + $b->{num_FAT_tables} * $b->{sectors_per_FAT}));
679 0           seek_and_read $fh, $off, $b->{root_dir_entries} * 0x20, $s;
680             }
681             }
682 0 0         if (defined $how->{parse_rootdir}) {
683 0           my($res, $f) = interpret_directory $s, $b->{guessed_FAT_flavor} == 32,
684             $how->{keep_del}, $how->{keep_dots}, $how->{keep_labels};
685 0 0 0       die "Directory ended in the middle of LFN" if ($res || 0) eq 'mid';
686 0           $out->{rootdir_files} = $f;
687 0           $out->{rootdir_ended} = $res;
688             }
689 0           $out->{rootdir_raw} = $s;
690             }
691 0 0 0       close $fh or die "close `$inif' for read: $!" if $close;
692 0           return $out;
693             }
694              
695             sub output_cluster_chain ($$$$$$;$) {
696 0   0 0 0   my($ifh, $ofh, $start, $size, $b, $FAT, $offset) =
697             (shift, shift, shift, shift, shift, shift, shift||0);
698 0 0         return unless $size;
699 0           my($L, $S) = ($b->{sector_size} * $b->{sectors_in_cluster},
700             $offset + $b->{sector_of_cluster0}*$b->{sector_size});
701             my $piper = sub ($$) {
702 0     0     my($start1, $len) = ($L * shift, $L * shift);
703             # warn "Piper: start=$start1, len=$len\n";
704 0 0         if ($len > $size) {
705 0 0         die "Cluster chain too long, len=$len, cl=$L, sz=$size" if $len - $L >= $size;
706 0           $len = $size;
707             }
708 0           while ($len) {
709 0 0         my $l = ($len > (1<<24)) ? (1<<24) : $len; # 16M chunks
710 0           my $s;
711 0           seek_and_read $ifh, $S + $start1, $l, $s;
712 0           syswrite $ofh, $s, length $s;
713 0           $len -= $l, $size -= $l, $start1 += $l;
714             }
715 0           };
716 0           my $sz = int(($size + $L - 1)/$L);
717 0 0         $piper->($start, $sz), return 1 if not defined $FAT;
718             # Inspect the last cluster for end of chain too
719 0           my ($total) = cluster_chain $start, $sz+1, $FAT, $b, $piper;
720 0 0         die "No end of cluster chain" unless $total;
721 0           1;
722             }
723              
724             sub read_cluster_chain ($$$$;$$) { # No size, as in dir...
725 0   0 0 0   my($ifh, $start, $b, $FAT, $offset, $exp_len) =
726             (shift, shift, shift, shift, shift||0, shift);
727 0           my($L, $S, $s) = ($b->{sector_size} * $b->{sectors_in_cluster},
728             $offset + $b->{sector_of_cluster0}*$b->{sector_size}, '');
729 0 0 0       (seek_and_read $ifh, $S + $L * $start, $exp_len, $s),
730             return \$s if not defined $FAT and defined $exp_len;
731             my $piper = sub ($$) {
732 0     0     my($start1, $l) = (shift, shift);
733 0           seek_and_read $ifh, $S + $L * $start1, $L * $l, $s, length $s;
734 0           };
735 0           my ($total) = cluster_chain $start, 0, $FAT, $b, $piper;
736 0 0         die "No end of cluster chain" unless $total;
737 0           \$s;
738             }
739              
740             sub write_file ($$$$$;$) {
741 0   0 0 1   my ($fh, $dir, $f, $b, $FAT, $offset) =
742             (shift, shift, shift, shift, shift, shift||0);
743 0 0 0       return if $f->{is_volume_label}
      0        
744             or $f->{name} eq 'EA DATA. SF' or $f->{name} eq 'WP ROOT. SF';
745 0 0         die "directory `$f->{name}' as file!" if $f->{is_subdir};
746 0           my $name = "$dir/$f->{name}";
747 0 0         open O, '>', $name or die "error opening $name for write: $!";
748 0           binmode O;
749 0           output_cluster_chain($fh, \*O, $f->{cluster}, $f->{size}, $b, $FAT, $offset);
750 0 0         close O or die "error closing $name for write: $!";
751 0 0         chmod 0555, $name if $f->{attrib} & 0x1; # read only
752             # unset archive mode?
753             }
754              
755             sub recurse_dir ($$$$$$$;$);
756             sub recurse_dir ($$$$$$$;$) {
757 0   0 0 0   my ($callbk, $path, $fh, $how, $f, $b, $FAT, $offset) =
758             (shift, shift, shift, shift, shift, shift, shift, shift||0);
759 0           my $files =
760             interpret_directory( $$f, $b->{guessed_FAT_flavor} == 32, $how->{keep_del},
761             $how->{keep_dots}, $how->{keep_labels} );
762 0           for my $file (@$files) {
763             # next if $file->{is_volume_label};
764 0           my $res = $callbk->($path, $file);
765 0 0 0       if ($res and $file->{is_subdir} and not $file->{deleted}
      0        
      0        
766             and $file->{name} !~ /^\.(\.)?$/) {
767 0           push @$path, $file->{name};
768 0           my $exp_len;
769 0 0         $exp_len = $b->{sector_size} * $b->{sectors_in_cluster}
770             unless defined $FAT; # XXXX Expect dir size of one cluster???
771 0           recurse_dir($callbk, $path, $fh, $how,
772             read_cluster_chain($fh, $file->{cluster}, $b, $FAT, $offset,
773             $exp_len),
774             $b, $FAT, $offset);
775 0           pop @$path;
776             }
777             }
778             }
779              
780             sub write_dir ($$$$$;$$$$) {
781 0   0 0 1   my ($fh, $o_root, $ff, $b, $FAT, $how, $depth, $offset, $exists) =
782             (shift, shift, shift, shift, shift, shift, shift||0, shift);
783 0 0         $depth = 1e100 unless defined $depth;
784             my $callbk = sub ($$) {
785 0     0     my($path,$f) = (shift, shift);
786 0 0 0       next if $f->{is_volume_label} or $f->{name} =~ /^\.(\.)?$/;
787 0           my $p = join '/', $o_root, @$path;
788 0 0         return write_file $fh, $p, $f, $b, $FAT, $offset unless $f->{is_subdir};
789 0 0         return 0 if @$path >= $depth;
790 0 0 0       mkdir "$p/$f->{name}", 0777 or die "mkdir `$p/$f->{name}': $!"
      0        
791             unless $exists and not @$path;
792 0           return 1;
793 0           };
794 0   0       recurse_dir($callbk, [], $fh, $how||{}, $ff, $b, $FAT, $offset);
795             }
796              
797             sub list_dir ($$$$;$$$) {
798 0   0 0 0   my ($fh, $ff, $b, $FAT, $how, $depth, $offset) =
799             (shift, shift, shift, shift, shift, shift, shift||0, shift);
800 0 0         $depth = 1e100 unless defined $depth;
801             my $callbk = sub ($$) {
802 0     0     my($path,$f,$pre) = (shift, shift, '');
803 0 0         print("# label=$f->{name}\n"), return if $f->{is_volume_label};
804 0           my $p = join '/', @$path, $f->{name};
805 0 0         $p .= '/' if $f->{is_subdir};
806 0 0         $pre = '#del ' if $f->{deleted};
807 0 0         $pre = '# ' if $f->{name} =~ /^\.(\.)?$/;
808 0           print "$pre$f->{attrib}\t$f->{size}\t$f->{date_write}/$f->{time_write}\t$f->{cluster}\t$p\n";
809 0           return @$path < $depth;
810 0           };
811 0   0       recurse_dir($callbk, [], $fh, $how||{}, $ff, $b, $FAT, $offset);
812             }
813              
814             # First FAT entry contains 0xFF*, the rest 0x0F*; so 0x2*, 0xA* do not conflict
815             sub compress_FAT ($$$) { # Down to 2-4 bytes/file after gzip...
816 0     0 0   my($FAT, $w, $fh) = (shift, shift, shift);
817 0           my ($c, $cc, $c0, $off, $ee, $remain, @out, $F) = (0, 0, 0, 0);
818 0           local $\ = '';
819 0           while (1) {
820 0 0         if (ref $FAT eq 'ARRAY') {
821 0           $F = $FAT, $remain = 0;
822             } else {
823 0 0         $remain = length $$FAT unless defined $remain;
824 0           my $l = $remain;
825 0 0         $l = 750000 if $l > 750000; # Should be divisible by 12...
826 0           FAT_2array($F = [], $FAT, $w, $off, $l);
827 0           $remain -= $l, $off += $l;
828             }
829 0           for my $e (@$F) {
830 0           $c++; # Next cluster
831 0 0         if ($e) {
832 0 0         (push @out, 0xA0000000 + $c0), $c0 = 0 if $c0;
833 0 0         $cc++, next if $e == $c;
834 0 0         (push @out, 0x20000000 + $cc), $cc = 0 if $cc;
835 0           push @out, $e;
836             } else {
837 0 0         (push @out, 0x20000000 + $cc), $cc = 0 if $cc;
838 0           $c0++, next;
839             }
840 0 0         (print $fh pack 'V*', @out), @out = () if @out > 1000;
841             }
842 0 0         last unless $remain;
843             }
844 0 0         push @out, 0xA0000000 + $c0 if $c0;
845 0 0         push @out, 0x20000000 + $cc if $cc;
846 0           print $fh pack 'V*', @out;
847             }
848              
849             sub _FAT_2string ($$$$) {
850 0     0     my($FAT, $w, $start, $c) = @_;
851 0 0         if ($w eq 12) {
852 0           my($out, $e) = ('', $start + $c);
853 0           while ($start < $e) { # Assume even
854 0           my $x = pack 'V', $FAT->[$start] + ($FAT->[$start+1]<<12);
855 0           $out .= substr $x, 0, 3;
856 0           $start += 2;
857             }
858 0           $out;
859             } else { # $w is 'V' or 'v'
860 0           pack "$w*", @$FAT[$start .. $start + $c - 1];
861             }
862             }
863              
864             sub output_FAT ($$$) {
865 0     0 0   my($FAT, $w, $fh, $s) = (shift, shift, shift, 0);
866 0           local $\ = '';
867 0 0         (print $fh $$FAT), return unless ref $FAT eq 'ARRAY';
868 0           my $c = @$FAT;
869 0 0         $w = (32 == $w) ? 'V' : 'v' if $w != 12;
    0          
870 0           while ($c) {
871 0 0         my $cc = ($c > 750000) ? 750000 : $c;
872 0           print $fh _FAT_2string($FAT, $w, $s, $cc);
873 0           $c -= $cc, $s += $cc;
874             }
875             }
876              
877             sub __emit ($$$) {
878 0     0     my($ofh, $out, $w) = (shift, shift, shift);
879 0           my $outc = @$out;
880 0           my $cut;
881 0 0 0       $cut = 1, $outc-- if $w eq 12 and $outc % 2;
882 0           print $ofh _FAT_2string($out, $w, 0, $outc);
883 0 0         @$out = $cut ? $$out[-1] : ();
884             }
885              
886             sub uncompress_FAT ($$$) {
887 0     0 0   my($ifh, $w, $ofh) = (shift, shift, shift);
888 0           my ($c, @f, @out, $F) = (0);
889 0           @f[0x2, 0xA] = (0x2, 0xA);
890 0           local $\ = '';
891 0 0         $w = (32 == $w) ? 'V' : 'v' if $w != 12;
    0          
892 0           while (1) {
893 0 0         last unless sysread $ifh, $F, 4*1e4;
894 0           for my $n (unpack 'V*', $F) {
895 0           my $n1 = $f[$n >> 28];
896 0 0         if ($n1) { # Special
897 0           my $cc = $n & 0xFFFFFFF;
898 0           while ($cc) {
899 0           my ($ccc, @rest) = $cc;
900 0 0         $ccc = 1e4 if $ccc > 1e4;
901 0 0         if ($n1 == 0x2) { # A run
902 0           push @out, $c + 1 .. $c + $ccc;
903             } else { # 0s
904 0           push @out, (0) x $ccc;
905             }
906 0           $cc -= $ccc, $c += $ccc;
907 0 0         __emit($ofh, \@out, $w) if @out >= 1e4;
908             }
909             } else {
910 0           $c++;
911 0           push @out, $n;
912             }
913             }
914 0 0         __emit($ofh, \@out, $w) if @out >= 1e4;
915             }
916 0           __emit($ofh, \@out, $w);
917 0 0         die "Odd number of uncompressed items, w=$w" if @out;
918             }
919              
920             1;
921             __END__