File Coverage

blib/lib/Sys/Export/VFAT.pm
Criterion Covered Total %
statement 474 536 88.4
branch 137 238 57.5
condition 99 190 52.1
subroutine 42 47 89.3
pod 19 19 100.0
total 771 1030 74.8


line stmt bran cond sub pod time code
1             package Sys::Export::VFAT;
2             # ABSTRACT: Write minimal FAT12/16/32 filesystems with control over stored file extents
3             our $VERSION = '0.005_002'; # TRIAL VERSION
4              
5 3     3   195519 use v5.26;
  3         8  
6 3     3   10 use warnings;
  3         5  
  3         160  
7 3     3   13 use experimental qw( signatures );
  3         3  
  3         14  
8 3     3   300 use Fcntl qw( S_IFDIR S_ISDIR S_ISREG );
  3         6  
  3         206  
9 3     3   13 use Scalar::Util qw( blessed dualvar refaddr weaken );
  3         6  
  3         129  
10 3     3   12 use List::Util qw( min max );
  3         3  
  3         147  
11 3     3   12 use POSIX 'ceil';
  3         3  
  3         24  
12 3     3   606 use Sys::Export::LogAny '$log';
  3         7  
  3         14  
13 3     3   993 use Encode qw( encode decode );
  3         11992  
  3         149  
14 3     3   12 use Carp;
  3         5  
  3         332  
15             our @CARP_NOT= qw( Sys::Export Sys::Export::Unix );
16             use constant {
17 3         311 ATTR_READONLY => dualvar(0x01, 'ATTR_READONLY'),
18             ATTR_HIDDEN => dualvar(0x02, 'ATTR_HIDDEN'),
19             ATTR_SYSTEM => dualvar(0x04, 'ATTR_SYSTEM'),
20             ATTR_VOLUME_ID => dualvar(0x08, 'ATTR_VOLUME_ID'),
21             ATTR_DIRECTORY => dualvar(0x10, 'ATTR_DIRECTORY'),
22             ATTR_ARCHIVE => dualvar(0x20, 'ATTR_ARCHIVE'),
23             ATTR_LONG_NAME => dualvar(0x0F, 'ATTR_LONG_NAME'),
24             ATTR_LONG_NAME_MASK => 0x3F,
25 3     3   14 };
  3         3  
26 3     3   12 use Exporter 'import';
  3         4  
  3         160  
27             our @EXPORT_OK= qw( FAT12 FAT16 FAT32 ATTR_READONLY ATTR_HIDDEN ATTR_SYSTEM ATTR_ARCHIVE
28             ATTR_VOLUME_ID ATTR_DIRECTORY is_valid_longname is_valid_shortname is_valid_volume_label
29             build_shortname remove_invalid_shortname_chars );
30 3     3   470 use Sys::Export qw( isa_hash isa_array isa_handle isa_int isa_pow2 expand_stat_shorthand write_file_extent );
  3         5  
  3         14  
31 3     3   1530 use Sys::Export::VFAT::Geometry qw( FAT12 FAT16 FAT32 );
  3         8  
  3         21175  
32             require Sys::Export::VFAT::AllocationTable;
33             require Sys::Export::VFAT::File;
34             require Sys::Export::VFAT::Directory;
35              
36              
37 31     31 1 103488 sub new($class, @attrs) {
  31         62  
  31         74  
  31         40  
38             my %attrs= @attrs != 1? @attrs
39 31 50       184 : isa_hash $attrs[0]? %{$attrs[0]}
  0 50       0  
    100          
40             : isa_handle $attrs[0]? ( filehandle => $attrs[0] )
41             : ( filename => $attrs[0] );
42 31         72 my $self= bless {}, $class;
43 31         93 $self->{root}= $self->_new_dir('/', undef, undef);
44             # keep root dir separate from subdirs
45 31         86 delete $self->{_subdirs}{refaddr $self->{root}};
46             # apply other attributes
47 31         150 $self->$_($attrs{$_}) for keys %attrs;
48 31         107 $self;
49             }
50              
51             # Create dir, and store a strong reference in ->{_subdirs}
52 65     65   81 sub _new_dir($self, $name, $parent, $file) {
  65         80  
  65         79  
  65         72  
  65         75  
  65         74  
53 65         331 my $dir= Sys::Export::VFAT::Directory->new(name => $name, parent => $parent, file => $file);
54 65         198 $self->{_subdirs}{refaddr $dir}= $dir;
55 65         122 $dir;
56             }
57              
58              
59 0 0   0 1 0 sub filename { @_ > 1? ($_[0]{filename}= $_[1]) : $_[0]{filename} }
60 93 100   93 1 426 sub filehandle { @_ > 1? ($_[0]{filehandle}= $_[1]) : $_[0]{filehandle} }
61              
62              
63 21262     21262 1 21841 sub root($self) { $self->{root} }
  21262         21093  
  21262         19827  
  21262         34478  
64 212     212 1 1672 sub geometry($self) { $self->{geometry} }
  212         194  
  212         202  
  212         513  
65 33     33 1 49 sub allocation_table($self) { $self->{allocation_table} }
  33         53  
  33         32  
  33         87  
66              
67 310     310 1 377 sub volume_offset($self, @val) {
  310         334  
  310         342  
  310         302  
68 310 100       615 if ($self->{geometry}) {
69 31 50       52 croak "Geometry already decided" if @val;
70 31         111 return $self->{geometry}->volume_offset;
71             }
72 279 100       453 if (@val) {
73 20 50       45 croak "volume_offset must be a multiple of 512" if $val[0] & 511;
74 20         59 return $self->{volume_offset}= $val[0];
75             }
76 259   100     896 $self->{volume_offset} // 0
77             }
78              
79              
80 256     256 1 240 sub min_bits($self, @val) {
  256         257  
  256         234  
  256         251  
81 256 50       325 if (@val) {
82 0 0       0 croak "Geometry already decided" if $self->{geometry};
83 0         0 $self->{min_bits}= $val[0];
84             }
85             $self->{min_bits}
86 256         840 }
87              
88 34     34 1 50 sub bytes_per_sector($self, @val) {
  34         42  
  34         40  
  34         37  
89 34 50       87 if ($self->{geometry}) {
90 0 0       0 croak "Geometry already decided" if @val;
91 0         0 return $self->{geometry}->bytes_per_sector;
92             }
93 34 100       61 if (@val) {
94 3 50       13 croak "bytes_per_sector must be a power of 2" unless isa_pow2 $val[0];
95 3         13 return $self->{bytes_per_sector}= $val[0];
96             }
97 31   100     103 $self->{bytes_per_sector} // 512
98             }
99              
100 37     37 1 48 sub sectors_per_cluster($self, @val) {
  37         35  
  37         41  
  37         43  
101 37 50       81 if ($self->{geometry}) {
102 0 0       0 croak "Geometry already decided" if @val;
103 0         0 return $self->{geometry}->sectors_per_cluster;
104             }
105 37 100       54 if (@val) {
106 3 50 33     11 croak "sectors_per_cluster must be a power of 2, and 128 or less" unless isa_pow2 $val[0] && $val[0] <= 128;
107 3         11 return $self->{sectors_per_cluster}= $val[0];
108             }
109             $self->{sectors_per_cluster}
110 34         93 }
111              
112 256     256 1 268 sub fat_count($self, @val) {
  256         273  
  256         254  
  256         253  
113 256 50       403 if ($self->{geometry}) {
114 0 0       0 croak "Geometry already decided" if @val;
115 0         0 return $self->{geometry}->fat_count;
116             }
117 256 50       401 if (@val) {
118 0 0 0     0 croak "fat_count must be positive" unless isa_int $val[0] && $val[0] > 0;
119 0         0 return $self->{fat_count}= $val[0];
120             }
121             $self->{fat_count}
122 256         521 }
123              
124 0     0 1 0 sub free_space($self, @val) {
  0         0  
  0         0  
  0         0  
125 0 0       0 if (@val) {
126 0 0       0 croak "Geometry already decided" if $self->{geometry};
127 0         0 return $self->{free_space}= $val[0];
128             }
129 0   0     0 $self->{free_space} // 0
130             }
131              
132 62     62 1 80 sub volume_label($self, @val) {
  62         62  
  62         63  
  62         59  
133 62 50       105 if (@val) {
134 0 0       0 croak "Invalid volume label '$val[0]'" unless is_valid_volume_label($val[0]);
135 0         0 return $self->{volume_label}= $val[0];
136             }
137             $self->{volume_label}
138 62         282 }
139              
140             # The smallest conceivable address where the data region could start
141             sub _minimum_offset_to_data {
142 1     1   7 state $minimum_offset_to_data= Sys::Export::VFAT::Geometry->new(
143             bytes_per_sector => 512,
144             sectors_per_cluster => 1,
145             fat_count => 1,
146             root_dirent_count => 1,
147             cluster_count => 1
148             )->data_start_offset;
149             }
150              
151              
152 10517     10517 1 34199 sub add($self, $spec) {
  10517         11486  
  10517         10455  
  10517         9563  
153 10517 100       19639 $spec= { expand_stat_shorthand($spec) }
154             if isa_array $spec;
155              
156             defined $spec->{uname} or defined $spec->{name}
157 10517 50 33     33730 or croak "Require 'uname' or 'name'";
158 10517 50       16592 defined $spec->{mode} or croak "Require 'mode'";
159              
160             # If user supplied uname, use that as a more official source of Unicode
161 10517   33     46872 my $path= $spec->{uname} // decode('UTF-8', $spec->{name}, Encode::FB_CROAK | Encode::LEAVE_SRC);
162 10517         268529 $path =~ s,^/,,; # remove leading slash, if any
163              
164 10517         75517 my @path= grep length, split '/', $path;
165 10517         18638 my $leaf= pop @path;
166              
167             # Walk through the tree based on the case-folded path
168 10517         19044 my $parent= $self->root;
169 10517         15470 for (@path) {
170 98362         121591 my $ent= $parent->entry($_);
171 98362 100       107729 if ($ent) {
172             croak $ent->name." is not a directory, while attempting to add '$path'"
173 98329 50       132576 unless $ent->{dir};
174             } else { # Auto-create directory. Autovivication is indicated by ->{file} = undef
175 33         52 $ent= $parent->add($_, undef);
176 33 100       48 my $name= ($parent == $self->root? '' : $parent->name)."/$_";
177 33         54 weaken($ent->{dir}= $self->_new_dir($name, $parent, undef));
178             }
179 98362         104124 $parent= $ent->{dir};
180             }
181              
182             # did user supply FAT attribute bitmask?
183 10517   33     18053 my $flags= $spec->{FAT_flags} // do {
184             # readonly determined by user -write bit of 'mode'
185 10517 50 33     37896 (!($spec->{mode} & 0400)? ATTR_READONLY : 0)
    50          
186             # hidden determined by leading '.' in filename
187             | (defined $leaf && $leaf =~ /^\./? ATTR_HIDDEN : 0)
188             };
189 10517         10777 my $file;
190 10517 100       22294 if (S_ISREG($spec->{mode})) {
    50          
191             my ($size, $offset, $align, $data_ref)
192 10516         12019 = @{$spec}{qw( size device_offset device_align data )};
  10516         19679  
193 10516 100 66     24139 $data_ref= do { my $x= $data_ref; \$x }
  1573         1698  
  1573         1977  
194             if defined $data_ref && !ref $data_ref;
195 10516 50       17471 if ($size) {
    50          
196             # ensure data matches
197 0 0       0 croak "File $path has size=$size but lacks 'data' attribute"
198             unless defined $data_ref;
199 0 0       0 croak "File $path ->{data} length disagrees with ->{size}"
200             unless length($$data_ref) == $size;
201             } elsif (defined $data_ref) {
202 10516   33     21217 $size //= length($$data_ref);
203             }
204             # must be a power of 2
205 10516 50 66     16516 croak "Invalid device_align $align for '$path', must be a power of 2"
206             if defined $align && !isa_pow2 $align;
207             # Sanity check device_offset before we get too far along
208 10516 100       15355 if (defined $offset) {
209 1   50     11 $align //= 512;
210             # must fall in the data area
211 1 50 33     3 $offset > $self->volume_offset + _minimum_offset_to_data
212             # must be a multiple of at least 512 (probably more)
213             && !($offset & ($align-1))
214             or croak "Invalid device_offset '$offset' for file '$path'";
215             }
216             $file= Sys::Export::VFAT::File->new(
217             name => "/$path", size => $size, flags => $flags,
218             align => $align, device_offset => $offset, data => $data_ref,
219 10516         47872 $spec->%{qw( atime btime mtime )},
220             );
221             } elsif (S_ISDIR($spec->{mode})) {
222 1         2 $flags |= ATTR_DIRECTORY;
223             # If adding this directory overtop a previous auto-vivified directory, the ->{file}
224             # will be empty and we can just update it.
225 1         4 my $cur= $parent->entry($leaf);
226             croak "Attempt to add duplicate directory $leaf"
227 1 0 33     2 if $cur && $cur->{file};
228             $file= Sys::Export::VFAT::File->new(
229 1         10 name => "/$path", size => 0, flags => $flags, $spec->%{qw( atime btime mtime )},
230             );
231 1 50       3 if ($cur) {
232 0         0 $cur->{file}= $file;
233 0         0 $cur->{dir}{file}= $file;
234 0         0 $log->debugf("updated attributes of %s", $path);
235 0         0 return $cur;
236             }
237             # otherwise, add this file to a directory entry
238             }
239             else {
240             # TODO: add conditional symlink support via hardlinks
241 0         0 croak "Can only export files or directories into VFAT"
242             }
243              
244             # this also checks for name collisions on shortname
245 10517         29826 my $ent= $parent->add($leaf, $file, shortname => $spec->{FAT_shortname});
246             # If the dirent is a directory, also add a directory object to the dirent
247 10517 100       21924 if ($file->is_dir) {
248             # the directory object also gets a reference to its file object.
249 1         3 weaken($ent->{dir}= $self->_new_dir("/$path", $parent, $file));
250             }
251              
252             $log->debugf("added %s longname=%s shortname=%s %s",
253             $path, $ent->{name}, $ent->{shortname}//'', join(' ',
254             !$ent->{file}? ('size=0 (empty file)')
255             : $ent->{file}->is_dir? ('DIR')
256             : (
257             (defined $ent->{file}->size? sprintf("size=0x%X", $ent->{file}->size) : 'size='),
258             (defined $ent->{file}->align? sprintf("device_align=0x%X", $ent->{file}->align) : ()),
259 10517 0 0     26544 (defined $ent->{file}->device_offset? sprintf("device_offset=0x%X", $ent->{file}->device_offset) : ())
    0          
    0          
    0          
    0          
    50          
260             )
261             ))
262             if $log->is_debug;
263              
264 10517         96348 $ent->{file};
265             }
266              
267              
268 31     31 1 113 sub finish($self) {
  31         37  
  31         39  
269 31         74 my $root= $self->root;
270 31         108 $log->debug('begin VFAT::finish');
271             # Find out the size of every directory, and build ->{_allocs}, ->{_dir_allocs} and ->{_special_allocs}
272 31         429 $self->_calc_dir_size($_) for $root, values $self->{_subdirs}->%*;
273             # calculate what geometry gives us the best size, when rounding each file to that cluster
274             # size vs. the size of the FAT it generates
275             my ($geom, $alloc)= $self->_optimize_geometry
276             or croak join("\n", "No geometry options can meet your device_offset / device_align requests:",
277             map "$_: $self->{_optimize_geometry_fail_reason}{$_}",
278 0         0 sort { $a <=> $b } keys $self->{_optimize_geometry_fail_reason}->%*
279 31 50       87 );
280 31         69 $self->{geometry}= $geom;
281 31         72 $self->{allocation_table}= $alloc;
282 31         108 $self->_commit_allocation; # copy cluster IDs into each of the File objects
283             # Pack directories now that all file cluster ids are known
284 31         173 $self->_pack_directory($_) for $root, values $self->{_subdirs}->%*;
285              
286 31         70 my $fh= $self->filehandle;
287 31 50       171 if (!$fh) {
288 0 0       0 defined $self->filename or croak "Must set filename or filehandle attributes";
289 0 0       0 open $fh, '+>', $self->filename
290             or croak "open: $!";
291             }
292             # check size
293 31 50       681 if (-s $fh < $geom->volume_offset + $geom->total_size) {
294 31         65 $log->debugf('resize output file to %s', $geom->volume_offset + $geom->total_size);
295 31 50       255 truncate($fh, $geom->volume_offset + $geom->total_size)
296             or croak "truncate: $!";
297             }
298 31         144 $self->_write_filesystem($fh, $geom, $alloc);
299 31 50       1188 unless ($self->filehandle) {
300 0 0       0 $fh->close or croak "close: $!";
301             }
302 31         195 $log->debug('end VFAT::finish');
303 31         335 1;
304             }
305              
306 0     0   0 sub _log_hexdump($buf) {
  0         0  
  0         0  
307             $log->tracef('%04X'.(" %02x"x16), $_, unpack 'C*', substr($buf, $_*16, 16))
308 0         0 for 0..ceil(length($buf) / 16);
309             }
310              
311 31     31   35 sub _write_filesystem($self, $fh, $geom, $alloc) {
  31         39  
  31         30  
  31         83  
  31         52  
  31         29  
312 31 50 50     96 ($alloc->max_cluster_id//-1) == ($geom->max_cluster_id//-1)
      50        
313             or croak "Max element of 'fat_entries' should be ".$geom->max_cluster_id.", but was ".$alloc->max_cluster_id;
314             # Pack the boot sector and other reserved sectors
315 31         98 my $buf= $self->_pack_reserved_sectors;
316 31         72 my $ofs= $self->volume_offset;
317 31         67 write_file_extent($fh, $ofs, $geom->reserved_size, \$buf, 0, 'reserved sectors');
318 31         86 $ofs += $geom->reserved_size;
319             # Pack the allocation tables
320 31         73 $buf= $self->_pack_allocation_table($alloc);
321             # store a copy of this into each of the regions occupied by fats
322 31         84 for (my $i= 0; $i < $geom->fat_count; $i++) {
323 62         104 write_file_extent($fh, $ofs, $geom->fat_size, \$buf, 0, "fat table $i");
324 62         141 $ofs += $geom->fat_size;
325             }
326             # For FAT12/FAT16, write the root directory entries
327 31 100       63 if ($geom->bits < FAT32) {
328 30         67 my $rootf= $self->root->file;
329             die "BUG: mis-sized FAT16 root directory"
330             if !$rootf->size || ($rootf->size & 31)
331 30 50 33     79 || length ${$rootf->data} != $rootf->size
  30   33     58  
      33        
332             || $rootf->size > $geom->root_dir_size;
333 30         76 write_file_extent($fh, $ofs, $geom->root_dir_size, $rootf->data, 0, 'root dir');
334             }
335             # The files and dirs have all been assigned clusters by _optimize_geometry
336 31         114 for my $cl (sort { $a <=> $b } keys $alloc->chains->%*) {
  114357         97972  
337 10551         21490 my ($invlist, $file)= $alloc->chains->{$cl}->@{'invlist','file'};
338 10551         19935 my $data= $file->data;
339             # Given an inversion list describing the allocated clusters for this file,
340             # write the relevant chunks of the file to those cluster data areas.
341 10551 50       20068 $log->debugf("writing '%s' at cluster %s", $file->name, _render_invlist($invlist))
342             if $log->is_debug;
343 10551         52212 my $data_ofs= 0;
344 10551         15461 for (my $i= 0; $i < @$invlist; $i += 2) {
345 10551         10076 my ($cl_start, $cl_lim)= @{$invlist}[$i, $i+1];
  10551         15236  
346 10551         20763 my $size= ($cl_lim-$cl_start) * $geom->bytes_per_cluster;
347 10551         15821 write_file_extent($fh, $geom->get_cluster_device_offset($cl_start), $size, $data, $data_ofs);
348 10551         25301 $data_ofs += $size;
349             }
350             }
351             }
352 0     0   0 sub _render_invlist($il) {
  0         0  
  0         0  
353 0 0       0 join ',',
354             map +($il->[$_*2] == $il->[$_*2+1]-1? $il->[$_*2] : $il->[$_*2] . '-' . $il->[$_*2+1]),
355             0 .. int($#$il/2)
356             }
357              
358              
359 10556     10556 1 14764 sub is_valid_longname($name) {
  10556         11506  
  10556         11129  
360             # characters permitted for LFN are all letters numbers and $%'-_@~`!(){}^#&+,;=[].
361             # and space and all codepoints above 0x7F.
362             # they may not begin with space, and cannot exceed 255 chars.
363 10556   33     50697 !!($name !~ /^(\.+\.?)\z/ # dot and dotdot are reserved
364             && $name =~ /^
365             [^\x00-\x20\x22\x2A\x2F\x3A\x3C\x3E\x3F\x5C\x7C\x7F]
366             [^\x00-\x1F\x22\x2A\x2F\x3A\x3C\x3E\x3F\x5C\x7C]{0,254}
367             \z/x);
368             }
369              
370 10602     10602 1 181810 sub is_valid_shortname($name) {
  10602         10790  
  10602         9633  
371 10602   100     69995 !!($name eq uc $name
372             && $name =~ /^
373             [\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D\x7E\x80-\xFF]
374             [\x20\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D\x7E\x80-\xFF]{0,7}
375             (?: \.
376             ( [\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D-\xFF]
377             [\x20\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D-\xFF]{0,2}
378             )?
379             )?
380             \z/x);
381             }
382              
383 0     0 1 0 sub is_valid_volume_label($name) {
  0         0  
  0         0  
384             # same as shortname but no '.' and space is allowed
385 0         0 !!($name =~ /^
386             [\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D-\xFF]
387             [\x20\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D-\xFF]{0,10}
388             \z/x);
389             }
390              
391 72     72 1 69 sub remove_invalid_shortname_chars($name, $replacement='_') {
  72         75  
  72         71  
  72         67  
392 72         85 $name =~ tr/a-z/A-Z/; # perform 'uc' but only for the ASCII range
393 72         141 $name =~ s/[^\x20\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D\x7E\x80-\xFF]+/$replacement/gr;
394             }
395              
396 31     31   39 sub _optimize_geometry($self) {
  31         48  
  31         34  
397             # calculate what geometry gives us the best size, when rounding each file to that cluster
398             # size vs. the size of the FAT it generates, and also meting the needs of alignment requests
399 31         54 my $root= $self->root;
400 31         55 my (@offsets, @aligned, @others);
401 31         129 my %seen= ( refaddr($root) => 1 );
402 31         83 for my $dir ($root, values $self->{_subdirs}->%*) {
403 65         118 for my $ent ($dir->entries->@*) {
404             # entry may have a directory ref and not a direct file ref
405 10550   33     16788 my $file= $ent->{file} //= $ent->{dir} && $ent->{dir}->file;
      66        
406 10550 50 33     35050 next unless $file && !$seen{refaddr $file}++;
407 10550 100       9393 push @{$file->device_offset? \@offsets : $file->align? \@aligned : \@others}, $file;
  10550 100       11919  
408             }
409             }
410 31         134 $log->debugf("_optimize_geometry offsets=%d aligned=%d others=%d",
411             scalar @offsets, scalar @aligned, scalar @others);
412             # provide stable results
413 31         309 @offsets= sort { $a->device_offset <=> $b->device_offset } @offsets;
  0         0  
414 31         63 @aligned= sort { fc $a->name cmp fc $b->name } @aligned;
  18         35  
415 31         235 @others= sort { fc $a->name cmp fc $b->name } @others;
  22625         27698  
416 31         96 my $min_ofs= min(map $_->device_offset, @offsets);
417 31         82 my $max_ofs= max(map $_->device_offset + $_->size, @offsets);
418 31         84 my $max_align= max(0, map $_->align, @aligned);
419 31         77 my $root_dirent_used= $root->file->size / 32;
420 31 50 33     103 isa_int $root_dirent_used && $root_dirent_used >= 1
421             or die "BUG: root must always have one entry";
422 31         99 my $bytes_per_sector= $self->bytes_per_sector;
423 31         69 my %fail_reason;
424             my $best;
425             # If the user defined sectors_per_cluster, we only have one option.
426             # Otherwise iterate through all of them to find the best.
427 31 100       66 my @spc= defined $self->sectors_per_cluster? ( $self->sectors_per_cluster )
428             : (1,2,4,8,16,32,64,128);
429 31         55 cluster_size: for my $sectors_per_cluster (@spc) {
430 227         305 my $cluster_size= $sectors_per_cluster * $bytes_per_sector;
431 227 50       512 isa_pow2 $cluster_size or die "BUG: cluster_size not a power of 2";
432             # Avoid triggering warning about incompatible cluster sizes if a good cluster size was
433             # already found.
434 227 100 100     606 last if $best && $cluster_size > 32*1024;
435             # Count total sectors used by ->{size} of files and dirs.
436             # Don't add root dir until we know it will be FAT32
437 199         234 my $clusters= 0;
438 199         387 for (@offsets, @aligned, @others) {
439 20000         25090 $clusters += ceil($_->size / $cluster_size);
440             }
441 199         675 $log->tracef("with sectors_per_cluster=%d, would require at least %d clusters",
442             $sectors_per_cluster, $clusters);
443 199   100     1889 $clusters ||= 1;
444 199         297 my ($reserved, $root_clusters_added);
445             # If file alignment is a larger power of 2 than cluster_size, then as long as data_start
446             # is aligned to cluster_size there will be a cluster that can satisfy the alignment.
447             # If file alignment is a smaller power of 2 than cluster_size, then as long as
448             # data_start is aligned to the file alignment, every cluster can satisfy the alignment.
449 199         447 my $align= min($cluster_size, $max_align);
450 199 100       353 if ($align) {
    100          
451             # But wait, does every device_offset meet this alignment? If not, give up.
452 133         204 for (@offsets) {
453 0 0       0 if ($_->device_offset & ($align-1)) {
454 0         0 $fail_reason{$sectors_per_cluster}= "device_offset ".$_->device_offset
455             ." of ".$_->name." conflicts with your alignment request of $align";
456 0         0 next cluster_size;
457             }
458             }
459             }
460             elsif (@offsets) {
461             # If not aligning clusters to pow2, might need to align to device_offset.
462             # First, every device_offset must have the same remainder modulo cluster_size.
463 7         9 my ($remainder, $prev);
464 7         13 for (@offsets) {
465 7         14 my $r= $_->device_offset & ($cluster_size-1);
466 7 50       16 if (!defined $remainder) {
    0          
467 7         9 $remainder= $r;
468 7         12 $prev= $_;
469             } elsif ($remainder != $r) {
470 0         0 $fail_reason{$sectors_per_cluster}= "file $_->{name} device_offset "
471             .$_->device_offset." modulo cluster_size $cluster_size conflicts with"
472             ." file ".$prev->name." device_offset ".$prev->device_offset;
473 0         0 next cluster_size;
474             }
475             }
476 7 50       12 if ($remainder) {
477 0         0 $align= [ $cluster_size, $remainder ];
478             } else {
479 7         10 $align= $cluster_size;
480             }
481             }
482             again_with_more_clusters: {
483             # If this number of clusters pushes us into FAT32, also need to add the root directory
484             # clusters to the count.
485 256 100 66     272 if (!$root_clusters_added
  256         760  
486             && $clusters > Sys::Export::VFAT::Geometry::FAT16_IDEAL_MAX_CLUSTERS()
487             ) {
488 1         9 $root_clusters_added= ceil($root->file->size / $cluster_size);
489 1         3 $clusters += $root_clusters_added;
490 1         3 $log->tracef("reached FAT32 threshold, adding %s clusters for root dir", $root_clusters_added);
491             }
492 256         526 my $geom= Sys::Export::VFAT::Geometry->new(
493             volume_offset => $self->volume_offset,
494             (align_clusters => $align)x!!$align,
495             bytes_per_sector => $bytes_per_sector,
496             sectors_per_cluster => $sectors_per_cluster,
497             fat_count => $self->fat_count,
498             cluster_count => $clusters,
499             used_root_dirent_count => $root_dirent_used,
500             min_bits => $self->min_bits,
501             );
502 256         590 $log->debugf("testing clusters=%d size=0x%X data_region=0x%X-%X min_ofs=0x%X max_ofs=0x%X",
503             $clusters, $cluster_size, $geom->data_start_device_offset, $geom->data_limit_device_offset,
504             $min_ofs, $max_ofs);
505 256 100 100     2459 if (@offsets || @aligned) {
506             # tables are too large? Try again with larger clusters.
507 197 50 66     471 if (defined $min_ofs && $min_ofs < $geom->data_start_device_offset) {
508 0         0 $fail_reason{$sectors_per_cluster}= "FAT tables too large for requested device_offset $min_ofs";
509 0         0 next cluster_size;
510             }
511             # Not enough clusters? Try again with more.
512 197 100 100     456 if (defined $max_ofs && $max_ofs > $geom->data_limit_device_offset) {
513             # This might overshoot a bit since the tables also grow and push forward the
514             # whole data area.
515 7         14 $clusters= ceil(($max_ofs - $geom->data_start_device_offset) / $cluster_size);
516 7         110 goto again_with_more_clusters;
517             }
518             }
519             # Now verify we have enough clusters by actually alocating them
520 249         725 my $alloc= Sys::Export::VFAT::AllocationTable->new;
521 249         294 my %assignment;
522 249 50       341 unless (eval {
523             $self->_alloc_file($geom, $alloc, $_)
524 249 100       557 for @offsets, @aligned, @others, ($geom->bits == FAT32? ($root->file) : ());
525 249         519 1
526             }) {
527 0         0 chomp($fail_reason{$sectors_per_cluster}= "$@");
528 0         0 next cluster_size;
529             }
530 249 100       469 if ($alloc->max_used_cluster_id > $geom->max_cluster_id) {
531 50         91 $clusters= $alloc->max_used_cluster_id-1;
532 50         983 goto again_with_more_clusters;
533             }
534             # Allocation worked, so clamp the allocator to this nmber of sectors
535 199         329 $alloc->max_cluster_id($geom->max_cluster_id);
536             # Is this the smallest option so far?
537 199 100 100     749 if (!$best || $best->{geom}->total_sector_count > $geom->total_sector_count) {
538 32         174 $best= { geom => $geom, alloc => $alloc, cluster_assignment => \%assignment };
539             }
540             }
541             } continue {
542             $log->tracef("%s", $fail_reason{$sectors_per_cluster})
543 199 50       653 if defined $fail_reason{$sectors_per_cluster};
544             }
545 31 50       49 if (!$best) {
546 0         0 $log->debug("no cluster size works");
547 0         0 $self->{_optimize_geometry_fail_reason}= \%fail_reason;
548 0         0 return;
549             }
550 31         78 $log->debugf("best cluster_size is %d", $best->{geom}->bytes_per_cluster);
551 31         275 return @{$best}{'geom','alloc'};
  31         4233  
552             }
553              
554             # reserve clusters for a file according to the align/offset needs of that file
555 20101     20101   17867 sub _alloc_file($self, $geom, $alloc, $file) {
  20101         17563  
  20101         17988  
  20101         17236  
  20101         19152  
  20101         17026  
556 20101 50       27900 my $sz= $file->size or do { carp "Attempt to allocate zero-length file"; return };
  0         0  
  0         0  
557 20101         29735 my $cl_count= POSIX::ceil($sz / $geom->bytes_per_cluster);
558 20101         19815 my $cl_start;
559 20101 100       25010 if ($file->device_offset) {
    100          
560 7         13 my ($cl, $n)= $geom->get_cluster_extent_of_device_extent($file->device_offset, $sz);
561 7   33     15 $cl_start= $alloc->alloc_range($cl, $cl_count)
562             // croak "Can't allocate $cl_count clusters from offset ".$file->device_offset;
563             } elsif ($file->align) {
564 359         597 my ($mul, $ofs)= $geom->get_cluster_alignment_of_device_alignment($file->align);
565 359   33     823 $cl_start= $alloc->alloc_contiguous($cl_count, $mul, $ofs)
566             // croak "Can't allocate $cl_count clusters aligned to ".$file->align;
567             } else {
568 19735   33     26050 $cl_start= $alloc->alloc($cl_count)
569             // croak "Can't allocate $cl_count clusters";
570             }
571 20101         25386 $alloc->{chains}{$cl_start}{file}= $file;
572 20101         31096 $cl_start;
573             }
574              
575             # store the cluster and device offset into the File objects
576 31     31   40 sub _commit_allocation($self) {
  31         30  
  31         31  
577 31         71 my $alloc= $self->allocation_table;
578 31         67 my $geom= $self->geometry;
579             # Apply file cluster IDs to the File objects
580 31         89 for (values $alloc->chains->%*) {
581 10551         17571 my $file= $_->{file};
582 10551         21439 $file->{cluster}= $_->{invlist}[0];
583             # Only set offset if file is contiguous
584             $file->{device_offset} //= $geom->get_cluster_device_offset($file->{cluster})
585 10551 50 66     9452 if 2 == @{$_->{invlist}};
  10551         30543  
586             }
587             }
588              
589             our @sector0_fields_common= (
590             [ BS_jmpBoot => 0, 3, 'a3', '' ],
591             [ BS_OEMName => 3, 8, 'a8', 'MSWIN4.1' ],
592             [ BPB_BytsPerSec => 11, 2, 'v' ],
593             [ BPB_SecPerClus => 13, 1, 'C' ],
594             [ BPB_RsvdSecCnt => 14, 2, 'v' ],
595             [ BPB_NumFATs => 16, 1, 'C' ],
596             [ BPB_RootEntCnt => 17, 2, 'v' ],
597             [ BPB_TotSec16 => 19, 2, 'v' ],
598             [ BPB_Media => 21, 1, 'C', 0xF8 ],
599             [ BPB_FATSz16 => 22, 2, 'v' ],
600             [ BPB_SecPerTrk => 24, 2, 'v', 0 ],
601             [ BPB_NumHeads => 26, 2, 'v', 0 ],
602             [ BPB_HiddSec => 28, 4, 'V', 0 ],
603             [ BPB_TotSec32 => 32, 4, 'V' ]
604             );
605             our @sector0_fat16_fields= (
606             @sector0_fields_common,
607             [ BS_DrvNum => 36, 1, 'C', 0x80 ],
608             [ BS_Reserved1 => 37, 1, 'C', 0 ],
609             [ BS_BootSig => 38, 1, 'C', 0x29 ],
610             [ BS_VolID => 39, 4, 'V' ],
611             [ BS_VolLab => 43, 11, 'A11', 'NO NAME' ],
612             [ BS_FilSysType => 54, 8, 'A8' ],
613             [ _signature => 510, 2, 'v', 0xAA55 ],
614             );
615             our @sector0_fat32_fields= (
616             @sector0_fields_common,
617             [ BPB_FATSz32 => 36, 4, 'V' ],
618             [ BPB_ExtFlags => 40, 2, 'v', 0 ],
619             [ BPB_FSVer => 42, 2, 'v', 0 ],
620             [ BPB_RootClus => 44, 4, 'V' ],
621             [ BPB_FSInfo => 48, 2, 'v' ],
622             [ BPB_BkBootSec => 50, 2, 'v', 0 ],
623             [ BPB_Reserved => 52, 12, 'a12', '' ],
624             [ BS_DrvNum => 64, 1, 'C', 0x80 ],
625             [ BS_Reserved1 => 65, 1, 'C', 0 ],
626             [ BS_BootSig => 66, 1, 'C', 0x29 ],
627             [ BS_VolID => 67, 4, 'V' ],
628             [ BS_VolLab => 71, 11, 'A11', 'NO NAME' ],
629             [ BS_FilSysType => 82, 8, 'A8' ],
630             [ _signature => 510, 2, 'v', 0xAA55 ],
631             );
632             our @fat32_fsinfo_fields= (
633             [ FSI_LeadSig => 0, 4, 'V', 0x41615252 ],
634             [ FSI_Reserved1 => 4,480, 'a480', '' ],
635             [ FSI_StrucSig => 484, 4, 'V', 0x61417272 ],
636             [ FSI_Free_Count => 488, 4, 'V', 0xFFFFFFFF ],
637             [ FSI_Nxt_Free => 492, 4, 'V', 0xFFFFFFFF ],
638             [ FSI_Reserved2 => 496, 12, 'a12', '' ],
639             [ FSI_TrailSig => 508, 4, 'V', 0xAA550000 ],
640             );
641 34     34   33 sub _append_pack_args($pack, $vals, $ofs, $fields, $attrs) {
  34         39  
  34         36  
  34         40  
  34         31  
  34         46  
  34         30  
642 34         107 for (@$fields) {
643 700         1248 push @$pack, '@'.($ofs+$_->[1]).$_->[3];
644 700   66     1400 push @$vals, $attrs->{$_->[0]} // $_->[4]
      33        
645             // croak "No value supplied for $_->[0], and no default";
646             }
647             }
648              
649 31947     31947   27664 sub _epoch_to_fat_date_time($epoch) {
  31947         31177  
  31947         26572  
650 31947         169681 my @lt = localtime($epoch);
651 31947         37809 my $year = $lt[5] + 1900;
652 31947         30036 my $mon = $lt[4] + 1;
653 31947         27935 my $mday = $lt[3];
654 31947         26924 my $hour = $lt[2];
655 31947         26530 my $min = $lt[1];
656 31947         33090 my $sec = int($lt[0] / 2); # 2-second resolution
657              
658 31947 50       37480 $year = 1980 if $year < 1980;
659 31947         34283 my $fat_date = (($year - 1980) << 9) | ($mon << 5) | $mday;
660 31947         32734 my $fat_time = ($hour << 11) | ($min << 5) | $sec;
661 31947         32355 my $fat_frac = ($epoch * 100) % 200;
662 31947         50045 return ($fat_date, $fat_time, $fat_frac);
663             }
664              
665             # This packs the boot sector and all the "reserved" sectors that appear before the
666             # beginning of the allocation tables.
667 31     31   36 sub _pack_reserved_sectors($self, %attrs) {
  31         28  
  31         36  
  31         31  
668 31         33 my (@pack, @vals);
669 31         61 my $geom= $self->geometry;
670 31         52 $attrs{BPB_BytsPerSec}= $geom->bytes_per_sector;
671 31         67 $attrs{BPB_SecPerClus}= $geom->sectors_per_cluster;
672 31         60 $attrs{BPB_RsvdSecCnt}= $geom->reserved_sector_count;
673 31         61 $attrs{BPB_NumFATs}= $geom->fat_count;
674 31         67 $attrs{BPB_RootEntCnt}= $geom->root_dirent_count;
675 31   33     170 $attrs{BS_VolLab} //= $self->volume_label;
676 31   33     141 $attrs{BS_VolID} //= time & 0xFFFFFFFF;
677 31 100       68 if ($geom->bits < FAT32) {
678 30 50       61 $attrs{BPB_FATSz16}= $geom->fat_sector_count < 0x10000? $geom->fat_sector_count : 0;
679 30 50       53 $attrs{BPB_FATSz32}= $geom->fat_sector_count < 0x10000? 0 : $geom->fat_sector_count;
680 30 50       60 $attrs{BPB_TotSec16}= $geom->total_sector_count < 0x10000? $geom->total_sector_count : 0;
681 30 50       72 $attrs{BPB_TotSec32}= $geom->total_sector_count < 0x10000? 0 : $geom->total_sector_count;
682 30 100       53 $attrs{BS_FilSysType}= $geom->bits == 12? 'FAT12' : 'FAT16';
683 30         103 _append_pack_args(\@pack, \@vals, 0, \@sector0_fat16_fields, \%attrs);
684             } else {
685             # Did the user specify location of fsinfo? If not, default to sector 1
686 1   50     7 $attrs{BPB_FSInfo} //= 1;
687 1   50     7 $attrs{BPB_BkBootSec} //= 2;
688 1         2 $attrs{BPB_FATSz16}= 0;
689 1         4 $attrs{BPB_FATSz32}= $geom->fat_sector_count;
690 1         4 $attrs{BPB_RootClus}= $self->root->file->cluster;
691 1         3 $attrs{BPB_TotSec16}= 0;
692 1         3 $attrs{BPB_TotSec32}= $geom->total_sector_count;
693 1         3 $attrs{BS_FilSysType}= "FAT";
694 1         6 _append_pack_args(\@pack, \@vals, 0, \@sector0_fat32_fields, \%attrs);
695            
696             # FSInfo struct, location is configurable
697 1         3 my $fsi_ofs= $attrs{BPB_FSInfo} * $attrs{BPB_BytsPerSec};
698 1   33     12 $attrs{FSI_Free_Count} //= $self->allocation_table->free_cluster_count;
699 1   33     77 $attrs{FSI_Nxt_Free} //= $self->allocation_table->first_free_cluster;
700 1         6 _append_pack_args(\@pack, \@vals, $fsi_ofs, \@fat32_fsinfo_fields, \%attrs);
701              
702             # Backup copy of boot sector
703 1         2 my $bk_ofs= $attrs{BPB_BkBootSec} * $attrs{BPB_BytsPerSec};
704 1         4 _append_pack_args(\@pack, \@vals, $bk_ofs, \@sector0_fat32_fields, \%attrs);
705 1         3 _append_pack_args(\@pack, \@vals, $bk_ofs+$fsi_ofs, \@fat32_fsinfo_fields, \%attrs);
706             }
707 31         465 pack join(' ', @pack), @vals;
708             }
709              
710             # This packs one allocation table and returns the buffer
711 31     31   34 sub _pack_allocation_table($self, $alloc) {
  31         36  
  31         34  
  31         33  
712 31         92 my $fat= [ $alloc->fat->@* ];
713 31 50 33     96 croak "Allocation table used more clusters than configured in geometry"
714             if $self->geometry->max_cluster_id < ($alloc->max_cluster_id // $alloc->max_used_cluster_id);
715 31         58 my $max= $self->geometry->max_cluster_id;
716 31         145 $#$fat= $max;
717 31         95 $fat->[$_]= 0x0FFFFFFF for 0,1;
718 31   100     22960 $fat->[$_] //= 0 for 2..$max; # prevent warnings in pack
719 31 100       60 if ($self->geometry->bits == 32) {
    100          
720 1         2643 return pack 'V*', @$fat;
721             } elsif ($self->geometry->bits == 16) {
722 1         157 return pack 'v*', @$fat;
723             } else {
724             # 12 bits per entry, pack in groups of 3 bytes, little-endian
725 29         50 my $buf= "\xFF\xFF\xFF";
726 29         97 for (my $i= 2; $i+1 <= $max; $i+= 2) {
727 1957         2205 my $v= ($fat->[$i] & 0xFFF) | ( ($fat->[$i+1] & 0xFFF) << 12 );
728 1957         2793 $buf .= pack 'vC', $v, ($v >> 16);
729             }
730 29 100       75 $buf .= pack 'v', $fat->[$max] & 0xFFF unless $max & 1;
731 29         141 return $buf;
732             }
733             }
734              
735             # This calculates the encoded size of one directory
736 65     65   74 sub _calc_dir_size($self, $dir) {
  65         77  
  65         70  
  65         71  
737             # If an autovivified directoy lacks a ->{file}, create it.
738 65   66     228 $dir->{file} //= Sys::Export::VFAT::File->new(name => $dir->name, flags => ATTR_DIRECTORY);
739 65         122 my $ents= $dir->entries;
740             # Need the 8.3 name in order to know whether it matches the long name
741 65         179 $dir->build_shortnames;
742             # root dir has a volume label ent, and all other dirs have '.' and '..'
743 65 100       135 my $n= @$ents + ($dir->is_root? 1 : 2);
744 65         108 for (@$ents) {
745             # Add LFN entries
746 10550 100       16554 if ($_->{name} ne $_->{shortname}) {
747 36         123 my $utf16= encode('UTF-16LE', $_->{name}, Encode::FB_CROAK|Encode::LEAVE_SRC);
748 36         3593 $n += ceil(length($utf16) / 26);
749             }
750             }
751             $log->debugf("dir %s has %d real entries, %d LFN entries, size=%d ents=%s",
752             $dir->name, scalar(@$ents), $n-scalar @$ents, $n*32,
753 65 0       140 [ map +( $_->{name} eq $_->{shortname}? $_->{name} : [ @{$_}{'name','shortname'} ] ), @$ents ])
  0 50       0  
754             if $log->is_debug;
755 65 50       454 croak "Directory ".$dir->name." exceeds maximum entry count ($n >= 65536)"
756             if $n >= 65536;
757 65         137 $dir->file->{size}= $n * 32; # always 32 bytes per dirent
758             }
759              
760             # Pack one directory and return the buffer
761 65     65   95 sub _pack_directory($self, $dir) {
  65         89  
  65         69  
  65         63  
762 65         87 my $data= '';
763 65 100 50     159 my @special= $dir->is_root? (
764             { short11 => $self->volume_label // 'NO NAME ', flags => ATTR_VOLUME_ID },
765             ) : (
766             { short11 => ".", flags => ATTR_DIRECTORY, file => $dir->file },
767             { short11 => '..', flags => ATTR_DIRECTORY, file => $dir->parent->file },
768             );
769 65         153 for my $ent (@special, sort { lc $a->{name} cmp lc $b->{name} } $dir->entries->@*) {
  22567         31502  
770 10649         11852 my ($name, $file, $shortname, $short11)= @{$ent}{qw( name file shortname short11 )};
  10649         23917  
771 10649   66     22164 $log->tracef("encoding dirent short=%-12s long=%s cluster=%s",
      100        
772             $shortname//$short11, $name, $file && $file->cluster);
773              
774 10649 100       55319 unless (length $short11) {
775 10550         15934 my ($base, $ext)= split /\./, $shortname;
776 10550   100     20453 $short11= pack 'A8 A3', $base, ($ext//'');
777             }
778 10649         14107 $short11 =~ s/^\xE5/\x05/; # \xE5 may occur in some charsets, and needs escaped
779              
780             # Need Long-File-Name entries?
781 10649 100 100     23642 if (defined $name && $name ne $shortname) {
782             # Checksum for directory shortname, used to verify long name parts
783 36         40 my $cksum= 0;
784             $cksum= ((($cksum >> 1) | ($cksum << 7)) + $_) & 0xFF
785 36         210 for unpack 'C*', $short11;
786             # Each dirent holds up to 26 bytes (13 chars) of the long name
787 36         204 my @chars= unpack 'v*', encode('UTF-16LE', $name, Encode::FB_CROAK|Encode::LEAVE_SRC);
788             # short final chunk is padded with \0\uFFFF*
789 36 50       651 if (my $remainder= @chars % 13) {
790 36         45 push @chars, 0;
791 36         86 push @chars, (0xFFFF)x(12 - $remainder);
792             }
793 36         94 my $last= ceil(@chars/13) - 1;
794 36         73 for my $i (reverse 0..$last) {
795 37         43 my $ofs= $i*13;
796 37 100       62 my $seq= ($i + 1) | (($i == $last) ? 0x40 : 0x00);
797 37         202 $data .= pack('C v5 C C C v6 v v2',
798             $seq, # sequence and end-flag
799             @chars[$ofs .. $ofs+4], # first 5 chars
800             0x0F, 0x00, $cksum, # attr = LFN, type = 0
801             @chars[$ofs+5 .. $ofs+10], # next 6 chars
802             0, # no cluster number
803             @chars[$ofs+11 .. $ofs+12] # last 2 chars
804             );
805             }
806             }
807              
808 10649   66     21322 my $mtime= $ent->{mtime} // ($file && $file->mtime) // time;
      33        
      33        
809 10649   66     20636 my $atime= $ent->{atime} // ($file && $file->atime) // $mtime;
      33        
      33        
810 10649   66     23363 my $btime= $ent->{btime} // ($file && $file->btime) // $mtime;
      33        
      33        
811 10649         13291 my ($wdate, $wtime) = _epoch_to_fat_date_time($mtime);
812 10649         12932 my ($cdate, $ctime, $ctime_frac)= _epoch_to_fat_date_time($btime);
813 10649         13332 my ($adate) = _epoch_to_fat_date_time($atime);
814 10649   33     24198 my $flags= $ent->{flags} // ($file && $file->flags) // 0;
      66        
      50        
815             # References to the root dir are always encoded as cluster zero, even on FAT32
816             # where the root dir actually lives at a nonzero cluster.
817             # Volume label also doesn't need a cluster id. Nor do empty files.
818 10649         10354 my $cluster= 0;
819 10649 100 100     20885 if ($file && $file != $self->root->file) {
820 10614   33     14955 $cluster= $file->cluster // croak "File ".$file->name." lacks a defined cluster id";
821             }
822             # Directories always written as size = 0
823 10649 100       17930 my $size= !$file? 0 : $file->is_dir? 0 : $file->size;
    100          
824 10649         20127 $log->tracef(" with encoded size=%d cluster=%d", $size, $cluster);
825 10649         79525 $data .= pack('A11 C C C v v v v v v v V',
826             $short11, $flags, 0, #NT_reserved
827             $ctime_frac, $ctime, $cdate, $adate,
828             $cluster >> 16, $wtime, $wdate, $cluster, $size);
829             }
830 65 50       115 die "BUG: calculated dir size ".$dir->file->size." != data length ".length($data)
831             unless $dir->file->size == length $data;
832             # Dir must be padded to length of sector/cluster with entries whose name begins with \x00
833             # but that will happen automatically later as the data is appended to the file.
834 65         134 $dir->file->{data}= \$data;
835             }
836              
837             # Avoiding dependency on namespace::clean
838             delete @{Sys::Export::VFAT::}{qw(
839             carp croak confess encode decode min max ceil blessed dualvar refaddr weaken S_ISDIR S_ISREG
840             expand_stat_shorthand isa_array isa_handle isa_hash isa_int isa_pow2 write_file_extent
841             )};
842             1;
843              
844             __END__