| 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'; # VERSION |
|
4
|
|
|
|
|
|
|
|
|
5
|
3
|
|
|
3
|
|
195659
|
use v5.26; |
|
|
3
|
|
|
|
|
7
|
|
|
6
|
3
|
|
|
3
|
|
10
|
use warnings; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
115
|
|
|
7
|
3
|
|
|
3
|
|
11
|
use experimental qw( signatures ); |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
22
|
|
|
8
|
3
|
|
|
3
|
|
337
|
use Fcntl qw( S_IFDIR S_ISDIR S_ISREG ); |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
233
|
|
|
9
|
3
|
|
|
3
|
|
13
|
use Scalar::Util qw( blessed dualvar refaddr weaken ); |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
115
|
|
|
10
|
3
|
|
|
3
|
|
9
|
use List::Util qw( min max ); |
|
|
3
|
|
|
|
|
12
|
|
|
|
3
|
|
|
|
|
152
|
|
|
11
|
3
|
|
|
3
|
|
12
|
use POSIX 'ceil'; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
21
|
|
|
12
|
3
|
|
|
3
|
|
585
|
use Sys::Export::LogAny '$log'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
18
|
|
|
13
|
3
|
|
|
3
|
|
962
|
use Encode qw( encode decode ); |
|
|
3
|
|
|
|
|
12780
|
|
|
|
3
|
|
|
|
|
169
|
|
|
14
|
3
|
|
|
3
|
|
13
|
use Carp; |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
315
|
|
|
15
|
|
|
|
|
|
|
our @CARP_NOT= qw( Sys::Export Sys::Export::Unix ); |
|
16
|
|
|
|
|
|
|
use constant { |
|
17
|
3
|
|
|
|
|
351
|
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
|
|
13
|
}; |
|
|
3
|
|
|
|
|
4
|
|
|
26
|
3
|
|
|
3
|
|
14
|
use Exporter 'import'; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
167
|
|
|
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
|
|
612
|
use Sys::Export qw( isa_hash isa_array isa_handle isa_int isa_pow2 expand_stat_shorthand write_file_extent ); |
|
|
3
|
|
|
|
|
16
|
|
|
|
3
|
|
|
|
|
16
|
|
|
31
|
3
|
|
|
3
|
|
1561
|
use Sys::Export::VFAT::Geometry qw( FAT12 FAT16 FAT32 ); |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
23362
|
|
|
32
|
|
|
|
|
|
|
require Sys::Export::VFAT::AllocationTable; |
|
33
|
|
|
|
|
|
|
require Sys::Export::VFAT::File; |
|
34
|
|
|
|
|
|
|
require Sys::Export::VFAT::Directory; |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
|
|
37
|
31
|
|
|
31
|
1
|
104058
|
sub new($class, @attrs) { |
|
|
31
|
|
|
|
|
56
|
|
|
|
31
|
|
|
|
|
60
|
|
|
|
31
|
|
|
|
|
48
|
|
|
38
|
|
|
|
|
|
|
my %attrs= @attrs != 1? @attrs |
|
39
|
31
|
50
|
|
|
|
167
|
: isa_hash $attrs[0]? %{$attrs[0]} |
|
|
0
|
50
|
|
|
|
0
|
|
|
|
|
100
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
: isa_handle $attrs[0]? ( filehandle => $attrs[0] ) |
|
41
|
|
|
|
|
|
|
: ( filename => $attrs[0] ); |
|
42
|
31
|
|
|
|
|
77
|
my $self= bless {}, $class; |
|
43
|
31
|
|
|
|
|
110
|
$self->{root}= $self->_new_dir('/', undef, undef); |
|
44
|
|
|
|
|
|
|
# keep root dir separate from subdirs |
|
45
|
31
|
|
|
|
|
97
|
delete $self->{_subdirs}{refaddr $self->{root}}; |
|
46
|
|
|
|
|
|
|
# apply other attributes |
|
47
|
31
|
|
|
|
|
129
|
$self->$_($attrs{$_}) for keys %attrs; |
|
48
|
31
|
|
|
|
|
99
|
$self; |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# Create dir, and store a strong reference in ->{_subdirs} |
|
52
|
65
|
|
|
65
|
|
82
|
sub _new_dir($self, $name, $parent, $file) { |
|
|
65
|
|
|
|
|
65
|
|
|
|
65
|
|
|
|
|
86
|
|
|
|
65
|
|
|
|
|
92
|
|
|
|
65
|
|
|
|
|
63
|
|
|
|
65
|
|
|
|
|
68
|
|
|
53
|
65
|
|
|
|
|
238
|
my $dir= Sys::Export::VFAT::Directory->new(name => $name, parent => $parent, file => $file); |
|
54
|
65
|
|
|
|
|
196
|
$self->{_subdirs}{refaddr $dir}= $dir; |
|
55
|
65
|
|
|
|
|
127
|
$dir; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
|
|
59
|
0
|
0
|
|
0
|
1
|
0
|
sub filename { @_ > 1? ($_[0]{filename}= $_[1]) : $_[0]{filename} } |
|
60
|
93
|
100
|
|
93
|
1
|
418
|
sub filehandle { @_ > 1? ($_[0]{filehandle}= $_[1]) : $_[0]{filehandle} } |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
|
63
|
21262
|
|
|
21262
|
1
|
21692
|
sub root($self) { $self->{root} } |
|
|
21262
|
|
|
|
|
20700
|
|
|
|
21262
|
|
|
|
|
19616
|
|
|
|
21262
|
|
|
|
|
40740
|
|
|
64
|
212
|
|
|
212
|
1
|
1573
|
sub geometry($self) { $self->{geometry} } |
|
|
212
|
|
|
|
|
233
|
|
|
|
212
|
|
|
|
|
196
|
|
|
|
212
|
|
|
|
|
492
|
|
|
65
|
33
|
|
|
33
|
1
|
36
|
sub allocation_table($self) { $self->{allocation_table} } |
|
|
33
|
|
|
|
|
41
|
|
|
|
33
|
|
|
|
|
30
|
|
|
|
33
|
|
|
|
|
104
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
310
|
|
|
310
|
1
|
392
|
sub volume_offset($self, @val) { |
|
|
310
|
|
|
|
|
317
|
|
|
|
310
|
|
|
|
|
337
|
|
|
|
310
|
|
|
|
|
299
|
|
|
68
|
310
|
100
|
|
|
|
627
|
if ($self->{geometry}) { |
|
69
|
31
|
50
|
|
|
|
58
|
croak "Geometry already decided" if @val; |
|
70
|
31
|
|
|
|
|
99
|
return $self->{geometry}->volume_offset; |
|
71
|
|
|
|
|
|
|
} |
|
72
|
279
|
100
|
|
|
|
422
|
if (@val) { |
|
73
|
20
|
50
|
|
|
|
49
|
croak "volume_offset must be a multiple of 512" if $val[0] & 511; |
|
74
|
20
|
|
|
|
|
56
|
return $self->{volume_offset}= $val[0]; |
|
75
|
|
|
|
|
|
|
} |
|
76
|
259
|
|
100
|
|
|
937
|
$self->{volume_offset} // 0 |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
|
|
80
|
256
|
|
|
256
|
1
|
282
|
sub min_bits($self, @val) { |
|
|
256
|
|
|
|
|
285
|
|
|
|
256
|
|
|
|
|
251
|
|
|
|
256
|
|
|
|
|
215
|
|
|
81
|
256
|
50
|
|
|
|
390
|
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
|
|
|
|
|
855
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
34
|
|
|
34
|
1
|
41
|
sub bytes_per_sector($self, @val) { |
|
|
34
|
|
|
|
|
45
|
|
|
|
34
|
|
|
|
|
37
|
|
|
|
34
|
|
|
|
|
48
|
|
|
89
|
34
|
50
|
|
|
|
97
|
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
|
|
|
|
57
|
if (@val) { |
|
94
|
3
|
50
|
|
|
|
7
|
croak "bytes_per_sector must be a power of 2" unless isa_pow2 $val[0]; |
|
95
|
3
|
|
|
|
|
8
|
return $self->{bytes_per_sector}= $val[0]; |
|
96
|
|
|
|
|
|
|
} |
|
97
|
31
|
|
100
|
|
|
109
|
$self->{bytes_per_sector} // 512 |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
37
|
|
|
37
|
1
|
69
|
sub sectors_per_cluster($self, @val) { |
|
|
37
|
|
|
|
|
39
|
|
|
|
37
|
|
|
|
|
34
|
|
|
|
37
|
|
|
|
|
42
|
|
|
101
|
37
|
50
|
|
|
|
72
|
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
|
|
|
|
64
|
if (@val) { |
|
106
|
3
|
50
|
33
|
|
|
15
|
croak "sectors_per_cluster must be a power of 2, and 128 or less" unless isa_pow2 $val[0] && $val[0] <= 128; |
|
107
|
3
|
|
|
|
|
15
|
return $self->{sectors_per_cluster}= $val[0]; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
$self->{sectors_per_cluster} |
|
110
|
34
|
|
|
|
|
126
|
} |
|
111
|
|
|
|
|
|
|
|
|
112
|
256
|
|
|
256
|
1
|
298
|
sub fat_count($self, @val) { |
|
|
256
|
|
|
|
|
325
|
|
|
|
256
|
|
|
|
|
246
|
|
|
|
256
|
|
|
|
|
236
|
|
|
113
|
256
|
50
|
|
|
|
382
|
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
|
|
|
|
353
|
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
|
|
|
|
|
529
|
} |
|
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
|
73
|
sub volume_label($self, @val) { |
|
|
62
|
|
|
|
|
62
|
|
|
|
62
|
|
|
|
|
75
|
|
|
|
62
|
|
|
|
|
68
|
|
|
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
|
|
|
|
|
304
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# The smallest conceivable address where the data region could start |
|
141
|
|
|
|
|
|
|
sub _minimum_offset_to_data { |
|
142
|
1
|
|
|
1
|
|
8
|
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
|
36408
|
sub add($self, $spec) { |
|
|
10517
|
|
|
|
|
10434
|
|
|
|
10517
|
|
|
|
|
9575
|
|
|
|
10517
|
|
|
|
|
9500
|
|
|
153
|
10517
|
100
|
|
|
|
22342
|
$spec= { expand_stat_shorthand($spec) } |
|
154
|
|
|
|
|
|
|
if isa_array $spec; |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
defined $spec->{uname} or defined $spec->{name} |
|
157
|
10517
|
50
|
33
|
|
|
29835
|
or croak "Require 'uname' or 'name'"; |
|
158
|
10517
|
50
|
|
|
|
15626
|
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
|
|
|
48634
|
my $path= $spec->{uname} // decode('UTF-8', $spec->{name}, Encode::FB_CROAK | Encode::LEAVE_SRC); |
|
162
|
10517
|
|
|
|
|
298882
|
$path =~ s,^/,,; # remove leading slash, if any |
|
163
|
|
|
|
|
|
|
|
|
164
|
10517
|
|
|
|
|
77914
|
my @path= grep length, split '/', $path; |
|
165
|
10517
|
|
|
|
|
19216
|
my $leaf= pop @path; |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# Walk through the tree based on the case-folded path |
|
168
|
10517
|
|
|
|
|
19675
|
my $parent= $self->root; |
|
169
|
10517
|
|
|
|
|
17077
|
for (@path) { |
|
170
|
98362
|
|
|
|
|
123272
|
my $ent= $parent->entry($_); |
|
171
|
98362
|
100
|
|
|
|
116423
|
if ($ent) { |
|
172
|
|
|
|
|
|
|
croak $ent->name." is not a directory, while attempting to add '$path'" |
|
173
|
98329
|
50
|
|
|
|
135340
|
unless $ent->{dir}; |
|
174
|
|
|
|
|
|
|
} else { # Auto-create directory. Autovivication is indicated by ->{file} = undef |
|
175
|
33
|
|
|
|
|
55
|
$ent= $parent->add($_, undef); |
|
176
|
33
|
100
|
|
|
|
45
|
my $name= ($parent == $self->root? '' : $parent->name)."/$_"; |
|
177
|
33
|
|
|
|
|
54
|
weaken($ent->{dir}= $self->_new_dir($name, $parent, undef)); |
|
178
|
|
|
|
|
|
|
} |
|
179
|
98362
|
|
|
|
|
108607
|
$parent= $ent->{dir}; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# did user supply FAT attribute bitmask? |
|
183
|
10517
|
|
33
|
|
|
18687
|
my $flags= $spec->{FAT_flags} // do { |
|
184
|
|
|
|
|
|
|
# readonly determined by user -write bit of 'mode' |
|
185
|
10517
|
50
|
33
|
|
|
40538
|
(!($spec->{mode} & 0400)? ATTR_READONLY : 0) |
|
|
|
50
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# hidden determined by leading '.' in filename |
|
187
|
|
|
|
|
|
|
| (defined $leaf && $leaf =~ /^\./? ATTR_HIDDEN : 0) |
|
188
|
|
|
|
|
|
|
}; |
|
189
|
10517
|
|
|
|
|
10417
|
my $file; |
|
190
|
10517
|
100
|
|
|
|
22944
|
if (S_ISREG($spec->{mode})) { |
|
|
|
50
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
my ($size, $offset, $align, $data_ref) |
|
192
|
10516
|
|
|
|
|
12874
|
= @{$spec}{qw( size device_offset device_align data )}; |
|
|
10516
|
|
|
|
|
20276
|
|
|
193
|
10516
|
100
|
66
|
|
|
25222
|
$data_ref= do { my $x= $data_ref; \$x } |
|
|
1573
|
|
|
|
|
1708
|
|
|
|
1573
|
|
|
|
|
1918
|
|
|
194
|
|
|
|
|
|
|
if defined $data_ref && !ref $data_ref; |
|
195
|
10516
|
50
|
|
|
|
16636
|
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
|
|
|
21462
|
$size //= length($$data_ref); |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
# must be a power of 2 |
|
205
|
10516
|
50
|
66
|
|
|
17207
|
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
|
|
|
|
14329
|
if (defined $offset) { |
|
209
|
1
|
|
50
|
|
|
14
|
$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
|
|
|
|
|
45295
|
$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
|
|
|
|
|
5
|
my $cur= $parent->entry($leaf); |
|
226
|
|
|
|
|
|
|
croak "Attempt to add duplicate directory $leaf" |
|
227
|
1
|
0
|
33
|
|
|
3
|
if $cur && $cur->{file}; |
|
228
|
|
|
|
|
|
|
$file= Sys::Export::VFAT::File->new( |
|
229
|
1
|
|
|
|
|
9
|
name => "/$path", size => 0, flags => $flags, $spec->%{qw( atime btime mtime )}, |
|
230
|
|
|
|
|
|
|
); |
|
231
|
1
|
50
|
|
|
|
4
|
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
|
|
|
|
|
29760
|
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
|
|
|
|
23228
|
if ($file->is_dir) { |
|
248
|
|
|
|
|
|
|
# the directory object also gets a reference to its file object. |
|
249
|
1
|
|
|
|
|
4
|
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
|
|
|
27828
|
(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
|
|
|
|
|
100797
|
$ent->{file}; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
|
|
268
|
31
|
|
|
31
|
1
|
107
|
sub finish($self) { |
|
|
31
|
|
|
|
|
36
|
|
|
|
31
|
|
|
|
|
34
|
|
|
269
|
31
|
|
|
|
|
56
|
my $root= $self->root; |
|
270
|
31
|
|
|
|
|
134
|
$log->debug('begin VFAT::finish'); |
|
271
|
|
|
|
|
|
|
# Find out the size of every directory, and build ->{_allocs}, ->{_dir_allocs} and ->{_special_allocs} |
|
272
|
31
|
|
|
|
|
476
|
$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
|
|
|
|
108
|
); |
|
280
|
31
|
|
|
|
|
72
|
$self->{geometry}= $geom; |
|
281
|
31
|
|
|
|
|
56
|
$self->{allocation_table}= $alloc; |
|
282
|
31
|
|
|
|
|
72
|
$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
|
|
|
|
|
157
|
$self->_pack_directory($_) for $root, values $self->{_subdirs}->%*; |
|
285
|
|
|
|
|
|
|
|
|
286
|
31
|
|
|
|
|
86
|
my $fh= $self->filehandle; |
|
287
|
31
|
50
|
|
|
|
183
|
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
|
|
|
|
668
|
if (-s $fh < $geom->volume_offset + $geom->total_size) { |
|
294
|
31
|
|
|
|
|
72
|
$log->debugf('resize output file to %s', $geom->volume_offset + $geom->total_size); |
|
295
|
31
|
50
|
|
|
|
236
|
truncate($fh, $geom->volume_offset + $geom->total_size) |
|
296
|
|
|
|
|
|
|
or croak "truncate: $!"; |
|
297
|
|
|
|
|
|
|
} |
|
298
|
31
|
|
|
|
|
136
|
$self->_write_filesystem($fh, $geom, $alloc); |
|
299
|
31
|
50
|
|
|
|
1329
|
unless ($self->filehandle) { |
|
300
|
0
|
0
|
|
|
|
0
|
$fh->close or croak "close: $!"; |
|
301
|
|
|
|
|
|
|
} |
|
302
|
31
|
|
|
|
|
207
|
$log->debug('end VFAT::finish'); |
|
303
|
31
|
|
|
|
|
342
|
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
|
|
34
|
sub _write_filesystem($self, $fh, $geom, $alloc) { |
|
|
31
|
|
|
|
|
40
|
|
|
|
31
|
|
|
|
|
32
|
|
|
|
31
|
|
|
|
|
35
|
|
|
|
31
|
|
|
|
|
32
|
|
|
|
31
|
|
|
|
|
40
|
|
|
312
|
31
|
50
|
50
|
|
|
173
|
($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
|
|
|
|
|
87
|
my $buf= $self->_pack_reserved_sectors; |
|
316
|
31
|
|
|
|
|
79
|
my $ofs= $self->volume_offset; |
|
317
|
31
|
|
|
|
|
68
|
write_file_extent($fh, $ofs, $geom->reserved_size, \$buf, 0, 'reserved sectors'); |
|
318
|
31
|
|
|
|
|
84
|
$ofs += $geom->reserved_size; |
|
319
|
|
|
|
|
|
|
# Pack the allocation tables |
|
320
|
31
|
|
|
|
|
72
|
$buf= $self->_pack_allocation_table($alloc); |
|
321
|
|
|
|
|
|
|
# store a copy of this into each of the regions occupied by fats |
|
322
|
31
|
|
|
|
|
91
|
for (my $i= 0; $i < $geom->fat_count; $i++) { |
|
323
|
62
|
|
|
|
|
106
|
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
|
|
|
|
58
|
if ($geom->bits < FAT32) { |
|
328
|
30
|
|
|
|
|
66
|
my $rootf= $self->root->file; |
|
329
|
|
|
|
|
|
|
die "BUG: mis-sized FAT16 root directory" |
|
330
|
|
|
|
|
|
|
if !$rootf->size || ($rootf->size & 31) |
|
331
|
30
|
50
|
33
|
|
|
87
|
|| length ${$rootf->data} != $rootf->size |
|
|
30
|
|
33
|
|
|
54
|
|
|
|
|
|
33
|
|
|
|
|
|
332
|
|
|
|
|
|
|
|| $rootf->size > $geom->root_dir_size; |
|
333
|
30
|
|
|
|
|
54
|
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
|
|
|
|
|
104
|
for my $cl (sort { $a <=> $b } keys $alloc->chains->%*) { |
|
|
114095
|
|
|
|
|
96531
|
|
|
337
|
10551
|
|
|
|
|
20312
|
my ($invlist, $file)= $alloc->chains->{$cl}->@{'invlist','file'}; |
|
338
|
10551
|
|
|
|
|
20294
|
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
|
|
|
|
21002
|
$log->debugf("writing '%s' at cluster %s", $file->name, _render_invlist($invlist)) |
|
342
|
|
|
|
|
|
|
if $log->is_debug; |
|
343
|
10551
|
|
|
|
|
55319
|
my $data_ofs= 0; |
|
344
|
10551
|
|
|
|
|
15377
|
for (my $i= 0; $i < @$invlist; $i += 2) { |
|
345
|
10551
|
|
|
|
|
10579
|
my ($cl_start, $cl_lim)= @{$invlist}[$i, $i+1]; |
|
|
10551
|
|
|
|
|
15321
|
|
|
346
|
10551
|
|
|
|
|
18931
|
my $size= ($cl_lim-$cl_start) * $geom->bytes_per_cluster; |
|
347
|
10551
|
|
|
|
|
15992
|
write_file_extent($fh, $geom->get_cluster_device_offset($cl_start), $size, $data, $data_ofs); |
|
348
|
10551
|
|
|
|
|
27405
|
$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
|
14959
|
sub is_valid_longname($name) { |
|
|
10556
|
|
|
|
|
11006
|
|
|
|
10556
|
|
|
|
|
11067
|
|
|
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
|
|
|
53968
|
!!($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
|
184916
|
sub is_valid_shortname($name) { |
|
|
10602
|
|
|
|
|
10936
|
|
|
|
10602
|
|
|
|
|
9237
|
|
|
371
|
10602
|
|
100
|
|
|
82382
|
!!($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
|
76
|
sub remove_invalid_shortname_chars($name, $replacement='_') { |
|
|
72
|
|
|
|
|
89
|
|
|
|
72
|
|
|
|
|
90
|
|
|
|
72
|
|
|
|
|
73
|
|
|
392
|
72
|
|
|
|
|
94
|
$name =~ tr/a-z/A-Z/; # perform 'uc' but only for the ASCII range |
|
393
|
72
|
|
|
|
|
195
|
$name =~ s/[^\x20\x21\x23-\x29\x2D\x30-\x39\x40-\x5A\x5E-\x7B\x7D\x7E\x80-\xFF]+/$replacement/gr; |
|
394
|
|
|
|
|
|
|
} |
|
395
|
|
|
|
|
|
|
|
|
396
|
31
|
|
|
31
|
|
41
|
sub _optimize_geometry($self) { |
|
|
31
|
|
|
|
|
39
|
|
|
|
31
|
|
|
|
|
32
|
|
|
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
|
|
|
|
|
53
|
my $root= $self->root; |
|
400
|
31
|
|
|
|
|
45
|
my (@offsets, @aligned, @others); |
|
401
|
31
|
|
|
|
|
116
|
my %seen= ( refaddr($root) => 1 ); |
|
402
|
31
|
|
|
|
|
78
|
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
|
|
|
17088
|
my $file= $ent->{file} //= $ent->{dir} && $ent->{dir}->file; |
|
|
|
|
66
|
|
|
|
|
|
406
|
10550
|
50
|
33
|
|
|
33967
|
next unless $file && !$seen{refaddr $file}++; |
|
407
|
10550
|
100
|
|
|
|
9057
|
push @{$file->device_offset? \@offsets : $file->align? \@aligned : \@others}, $file; |
|
|
10550
|
100
|
|
|
|
12009
|
|
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
} |
|
410
|
31
|
|
|
|
|
131
|
$log->debugf("_optimize_geometry offsets=%d aligned=%d others=%d", |
|
411
|
|
|
|
|
|
|
scalar @offsets, scalar @aligned, scalar @others); |
|
412
|
|
|
|
|
|
|
# provide stable results |
|
413
|
31
|
|
|
|
|
294
|
@offsets= sort { $a->device_offset <=> $b->device_offset } @offsets; |
|
|
0
|
|
|
|
|
0
|
|
|
414
|
31
|
|
|
|
|
88
|
@aligned= sort { fc $a->name cmp fc $b->name } @aligned; |
|
|
18
|
|
|
|
|
47
|
|
|
415
|
31
|
|
|
|
|
236
|
@others= sort { fc $a->name cmp fc $b->name } @others; |
|
|
23008
|
|
|
|
|
28678
|
|
|
416
|
31
|
|
|
|
|
97
|
my $min_ofs= min(map $_->device_offset, @offsets); |
|
417
|
31
|
|
|
|
|
78
|
my $max_ofs= max(map $_->device_offset + $_->size, @offsets); |
|
418
|
31
|
|
|
|
|
74
|
my $max_align= max(0, map $_->align, @aligned); |
|
419
|
31
|
|
|
|
|
81
|
my $root_dirent_used= $root->file->size / 32; |
|
420
|
31
|
50
|
33
|
|
|
127
|
isa_int $root_dirent_used && $root_dirent_used >= 1 |
|
421
|
|
|
|
|
|
|
or die "BUG: root must always have one entry"; |
|
422
|
31
|
|
|
|
|
87
|
my $bytes_per_sector= $self->bytes_per_sector; |
|
423
|
31
|
|
|
|
|
53
|
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
|
|
|
|
82
|
my @spc= defined $self->sectors_per_cluster? ( $self->sectors_per_cluster ) |
|
428
|
|
|
|
|
|
|
: (1,2,4,8,16,32,64,128); |
|
429
|
31
|
|
|
|
|
57
|
cluster_size: for my $sectors_per_cluster (@spc) { |
|
430
|
227
|
|
|
|
|
301
|
my $cluster_size= $sectors_per_cluster * $bytes_per_sector; |
|
431
|
227
|
50
|
|
|
|
600
|
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
|
|
|
607
|
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
|
|
|
|
|
223
|
my $clusters= 0; |
|
438
|
199
|
|
|
|
|
428
|
for (@offsets, @aligned, @others) { |
|
439
|
20000
|
|
|
|
|
25839
|
$clusters += ceil($_->size / $cluster_size); |
|
440
|
|
|
|
|
|
|
} |
|
441
|
199
|
|
|
|
|
739
|
$log->tracef("with sectors_per_cluster=%d, would require at least %d clusters", |
|
442
|
|
|
|
|
|
|
$sectors_per_cluster, $clusters); |
|
443
|
199
|
|
100
|
|
|
1978
|
$clusters ||= 1; |
|
444
|
199
|
|
|
|
|
229
|
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
|
|
|
|
|
384
|
my $align= min($cluster_size, $max_align); |
|
450
|
199
|
100
|
|
|
|
373
|
if ($align) { |
|
|
|
100
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# But wait, does every device_offset meet this alignment? If not, give up. |
|
452
|
133
|
|
|
|
|
200
|
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
|
|
|
|
|
9
|
for (@offsets) { |
|
465
|
7
|
|
|
|
|
16
|
my $r= $_->device_offset & ($cluster_size-1); |
|
466
|
7
|
50
|
|
|
|
16
|
if (!defined $remainder) { |
|
|
|
0
|
|
|
|
|
|
|
467
|
7
|
|
|
|
|
8
|
$remainder= $r; |
|
468
|
7
|
|
|
|
|
9
|
$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
|
|
|
|
16
|
if ($remainder) { |
|
477
|
0
|
|
|
|
|
0
|
$align= [ $cluster_size, $remainder ]; |
|
478
|
|
|
|
|
|
|
} else { |
|
479
|
7
|
|
|
|
|
8
|
$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
|
|
|
280
|
if (!$root_clusters_added |
|
|
256
|
|
|
|
|
663
|
|
|
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
|
|
|
|
|
4
|
$clusters += $root_clusters_added; |
|
490
|
1
|
|
|
|
|
6
|
$log->tracef("reached FAT32 threshold, adding %s clusters for root dir", $root_clusters_added); |
|
491
|
|
|
|
|
|
|
} |
|
492
|
256
|
|
|
|
|
605
|
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
|
|
|
|
|
591
|
$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
|
|
|
2452
|
if (@offsets || @aligned) { |
|
506
|
|
|
|
|
|
|
# tables are too large? Try again with larger clusters. |
|
507
|
197
|
50
|
66
|
|
|
505
|
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
|
|
|
475
|
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
|
|
|
|
|
112
|
goto again_with_more_clusters; |
|
517
|
|
|
|
|
|
|
} |
|
518
|
|
|
|
|
|
|
} |
|
519
|
|
|
|
|
|
|
# Now verify we have enough clusters by actually alocating them |
|
520
|
249
|
|
|
|
|
694
|
my $alloc= Sys::Export::VFAT::AllocationTable->new; |
|
521
|
249
|
|
|
|
|
313
|
my %assignment; |
|
522
|
249
|
50
|
|
|
|
305
|
unless (eval { |
|
523
|
|
|
|
|
|
|
$self->_alloc_file($geom, $alloc, $_) |
|
524
|
249
|
100
|
|
|
|
543
|
for @offsets, @aligned, @others, ($geom->bits == FAT32? ($root->file) : ()); |
|
525
|
249
|
|
|
|
|
544
|
1 |
|
526
|
|
|
|
|
|
|
}) { |
|
527
|
0
|
|
|
|
|
0
|
chomp($fail_reason{$sectors_per_cluster}= "$@"); |
|
528
|
0
|
|
|
|
|
0
|
next cluster_size; |
|
529
|
|
|
|
|
|
|
} |
|
530
|
249
|
100
|
|
|
|
519
|
if ($alloc->max_used_cluster_id > $geom->max_cluster_id) { |
|
531
|
50
|
|
|
|
|
95
|
$clusters= $alloc->max_used_cluster_id-1; |
|
532
|
50
|
|
|
|
|
956
|
goto again_with_more_clusters; |
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
# Allocation worked, so clamp the allocator to this nmber of sectors |
|
535
|
199
|
|
|
|
|
303
|
$alloc->max_cluster_id($geom->max_cluster_id); |
|
536
|
|
|
|
|
|
|
# Is this the smallest option so far? |
|
537
|
199
|
100
|
100
|
|
|
689
|
if (!$best || $best->{geom}->total_sector_count > $geom->total_sector_count) { |
|
538
|
32
|
|
|
|
|
175
|
$best= { geom => $geom, alloc => $alloc, cluster_assignment => \%assignment }; |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
} |
|
541
|
|
|
|
|
|
|
} continue { |
|
542
|
|
|
|
|
|
|
$log->tracef("%s", $fail_reason{$sectors_per_cluster}) |
|
543
|
199
|
50
|
|
|
|
597
|
if defined $fail_reason{$sectors_per_cluster}; |
|
544
|
|
|
|
|
|
|
} |
|
545
|
31
|
50
|
|
|
|
58
|
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
|
|
|
|
|
77
|
$log->debugf("best cluster_size is %d", $best->{geom}->bytes_per_cluster); |
|
551
|
31
|
|
|
|
|
338
|
return @{$best}{'geom','alloc'}; |
|
|
31
|
|
|
|
|
4255
|
|
|
552
|
|
|
|
|
|
|
} |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# reserve clusters for a file according to the align/offset needs of that file |
|
555
|
20101
|
|
|
20101
|
|
19579
|
sub _alloc_file($self, $geom, $alloc, $file) { |
|
|
20101
|
|
|
|
|
17488
|
|
|
|
20101
|
|
|
|
|
17562
|
|
|
|
20101
|
|
|
|
|
16655
|
|
|
|
20101
|
|
|
|
|
18088
|
|
|
|
20101
|
|
|
|
|
16114
|
|
|
556
|
20101
|
50
|
|
|
|
27447
|
my $sz= $file->size or do { carp "Attempt to allocate zero-length file"; return }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
557
|
20101
|
|
|
|
|
28255
|
my $cl_count= POSIX::ceil($sz / $geom->bytes_per_cluster); |
|
558
|
20101
|
|
|
|
|
19319
|
my $cl_start; |
|
559
|
20101
|
100
|
|
|
|
24310
|
if ($file->device_offset) { |
|
|
|
100
|
|
|
|
|
|
|
560
|
7
|
|
|
|
|
11
|
my ($cl, $n)= $geom->get_cluster_extent_of_device_extent($file->device_offset, $sz); |
|
561
|
7
|
|
33
|
|
|
20
|
$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
|
|
|
|
|
558
|
my ($mul, $ofs)= $geom->get_cluster_alignment_of_device_alignment($file->align); |
|
565
|
359
|
|
33
|
|
|
814
|
$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
|
|
|
25704
|
$cl_start= $alloc->alloc($cl_count) |
|
569
|
|
|
|
|
|
|
// croak "Can't allocate $cl_count clusters"; |
|
570
|
|
|
|
|
|
|
} |
|
571
|
20101
|
|
|
|
|
26011
|
$alloc->{chains}{$cl_start}{file}= $file; |
|
572
|
20101
|
|
|
|
|
33964
|
$cl_start; |
|
573
|
|
|
|
|
|
|
} |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# store the cluster and device offset into the File objects |
|
576
|
31
|
|
|
31
|
|
31
|
sub _commit_allocation($self) { |
|
|
31
|
|
|
|
|
33
|
|
|
|
31
|
|
|
|
|
29
|
|
|
577
|
31
|
|
|
|
|
69
|
my $alloc= $self->allocation_table; |
|
578
|
31
|
|
|
|
|
71
|
my $geom= $self->geometry; |
|
579
|
|
|
|
|
|
|
# Apply file cluster IDs to the File objects |
|
580
|
31
|
|
|
|
|
85
|
for (values $alloc->chains->%*) { |
|
581
|
10551
|
|
|
|
|
20230
|
my $file= $_->{file}; |
|
582
|
10551
|
|
|
|
|
25110
|
$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
|
|
|
9632
|
if 2 == @{$_->{invlist}}; |
|
|
10551
|
|
|
|
|
24712
|
|
|
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
|
|
60
|
sub _append_pack_args($pack, $vals, $ofs, $fields, $attrs) { |
|
|
34
|
|
|
|
|
36
|
|
|
|
34
|
|
|
|
|
32
|
|
|
|
34
|
|
|
|
|
38
|
|
|
|
34
|
|
|
|
|
41
|
|
|
|
34
|
|
|
|
|
41
|
|
|
|
34
|
|
|
|
|
30
|
|
|
642
|
34
|
|
|
|
|
83
|
for (@$fields) { |
|
643
|
700
|
|
|
|
|
1245
|
push @$pack, '@'.($ofs+$_->[1]).$_->[3]; |
|
644
|
700
|
|
66
|
|
|
1460
|
push @$vals, $attrs->{$_->[0]} // $_->[4] |
|
|
|
|
33
|
|
|
|
|
|
645
|
|
|
|
|
|
|
// croak "No value supplied for $_->[0], and no default"; |
|
646
|
|
|
|
|
|
|
} |
|
647
|
|
|
|
|
|
|
} |
|
648
|
|
|
|
|
|
|
|
|
649
|
31947
|
|
|
31947
|
|
29030
|
sub _epoch_to_fat_date_time($epoch) { |
|
|
31947
|
|
|
|
|
28987
|
|
|
|
31947
|
|
|
|
|
26407
|
|
|
650
|
31947
|
|
|
|
|
185328
|
my @lt = localtime($epoch); |
|
651
|
31947
|
|
|
|
|
38974
|
my $year = $lt[5] + 1900; |
|
652
|
31947
|
|
|
|
|
30581
|
my $mon = $lt[4] + 1; |
|
653
|
31947
|
|
|
|
|
27383
|
my $mday = $lt[3]; |
|
654
|
31947
|
|
|
|
|
26936
|
my $hour = $lt[2]; |
|
655
|
31947
|
|
|
|
|
26607
|
my $min = $lt[1]; |
|
656
|
31947
|
|
|
|
|
34869
|
my $sec = int($lt[0] / 2); # 2-second resolution |
|
657
|
|
|
|
|
|
|
|
|
658
|
31947
|
50
|
|
|
|
40455
|
$year = 1980 if $year < 1980; |
|
659
|
31947
|
|
|
|
|
35168
|
my $fat_date = (($year - 1980) << 9) | ($mon << 5) | $mday; |
|
660
|
31947
|
|
|
|
|
33950
|
my $fat_time = ($hour << 11) | ($min << 5) | $sec; |
|
661
|
31947
|
|
|
|
|
30843
|
my $fat_frac = ($epoch * 100) % 200; |
|
662
|
31947
|
|
|
|
|
56504
|
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
|
|
35
|
sub _pack_reserved_sectors($self, %attrs) { |
|
|
31
|
|
|
|
|
30
|
|
|
|
31
|
|
|
|
|
36
|
|
|
|
31
|
|
|
|
|
30
|
|
|
668
|
31
|
|
|
|
|
35
|
my (@pack, @vals); |
|
669
|
31
|
|
|
|
|
78
|
my $geom= $self->geometry; |
|
670
|
31
|
|
|
|
|
62
|
$attrs{BPB_BytsPerSec}= $geom->bytes_per_sector; |
|
671
|
31
|
|
|
|
|
68
|
$attrs{BPB_SecPerClus}= $geom->sectors_per_cluster; |
|
672
|
31
|
|
|
|
|
72
|
$attrs{BPB_RsvdSecCnt}= $geom->reserved_sector_count; |
|
673
|
31
|
|
|
|
|
79
|
$attrs{BPB_NumFATs}= $geom->fat_count; |
|
674
|
31
|
|
|
|
|
54
|
$attrs{BPB_RootEntCnt}= $geom->root_dirent_count; |
|
675
|
31
|
|
33
|
|
|
153
|
$attrs{BS_VolLab} //= $self->volume_label; |
|
676
|
31
|
|
33
|
|
|
119
|
$attrs{BS_VolID} //= time & 0xFFFFFFFF; |
|
677
|
31
|
100
|
|
|
|
58
|
if ($geom->bits < FAT32) { |
|
678
|
30
|
50
|
|
|
|
52
|
$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
|
|
|
|
61
|
$attrs{BPB_TotSec16}= $geom->total_sector_count < 0x10000? $geom->total_sector_count : 0; |
|
681
|
30
|
50
|
|
|
|
50
|
$attrs{BPB_TotSec32}= $geom->total_sector_count < 0x10000? 0 : $geom->total_sector_count; |
|
682
|
30
|
100
|
|
|
|
61
|
$attrs{BS_FilSysType}= $geom->bits == 12? 'FAT12' : 'FAT16'; |
|
683
|
30
|
|
|
|
|
106
|
_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
|
|
|
8
|
$attrs{BPB_FSInfo} //= 1; |
|
687
|
1
|
|
50
|
|
|
5
|
$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
|
|
|
|
|
13
|
$attrs{BPB_TotSec16}= 0; |
|
692
|
1
|
|
|
|
|
5
|
$attrs{BPB_TotSec32}= $geom->total_sector_count; |
|
693
|
1
|
|
|
|
|
3
|
$attrs{BS_FilSysType}= "FAT"; |
|
694
|
1
|
|
|
|
|
19
|
_append_pack_args(\@pack, \@vals, 0, \@sector0_fat32_fields, \%attrs); |
|
695
|
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
# FSInfo struct, location is configurable |
|
697
|
1
|
|
|
|
|
2
|
my $fsi_ofs= $attrs{BPB_FSInfo} * $attrs{BPB_BytsPerSec}; |
|
698
|
1
|
|
33
|
|
|
14
|
$attrs{FSI_Free_Count} //= $self->allocation_table->free_cluster_count; |
|
699
|
1
|
|
33
|
|
|
5
|
$attrs{FSI_Nxt_Free} //= $self->allocation_table->first_free_cluster; |
|
700
|
1
|
|
|
|
|
5
|
_append_pack_args(\@pack, \@vals, $fsi_ofs, \@fat32_fsinfo_fields, \%attrs); |
|
701
|
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
# Backup copy of boot sector |
|
703
|
1
|
|
|
|
|
3
|
my $bk_ofs= $attrs{BPB_BkBootSec} * $attrs{BPB_BytsPerSec}; |
|
704
|
1
|
|
|
|
|
3
|
_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
|
|
|
|
|
499
|
pack join(' ', @pack), @vals; |
|
708
|
|
|
|
|
|
|
} |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
# This packs one allocation table and returns the buffer |
|
711
|
31
|
|
|
31
|
|
33
|
sub _pack_allocation_table($self, $alloc) { |
|
|
31
|
|
|
|
|
37
|
|
|
|
31
|
|
|
|
|
34
|
|
|
|
31
|
|
|
|
|
34
|
|
|
712
|
31
|
|
|
|
|
97
|
my $fat= [ $alloc->fat->@* ]; |
|
713
|
31
|
50
|
33
|
|
|
80
|
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
|
|
|
|
|
54
|
my $max= $self->geometry->max_cluster_id; |
|
716
|
31
|
|
|
|
|
124
|
$#$fat= $max; |
|
717
|
31
|
|
|
|
|
97
|
$fat->[$_]= 0x0FFFFFFF for 0,1; |
|
718
|
31
|
|
100
|
|
|
26351
|
$fat->[$_] //= 0 for 2..$max; # prevent warnings in pack |
|
719
|
31
|
100
|
|
|
|
81
|
if ($self->geometry->bits == 32) { |
|
|
|
100
|
|
|
|
|
|
|
720
|
1
|
|
|
|
|
2669
|
return pack 'V*', @$fat; |
|
721
|
|
|
|
|
|
|
} elsif ($self->geometry->bits == 16) { |
|
722
|
1
|
|
|
|
|
164
|
return pack 'v*', @$fat; |
|
723
|
|
|
|
|
|
|
} else { |
|
724
|
|
|
|
|
|
|
# 12 bits per entry, pack in groups of 3 bytes, little-endian |
|
725
|
29
|
|
|
|
|
43
|
my $buf= "\xFF\xFF\xFF"; |
|
726
|
29
|
|
|
|
|
73
|
for (my $i= 2; $i+1 <= $max; $i+= 2) { |
|
727
|
1957
|
|
|
|
|
2173
|
my $v= ($fat->[$i] & 0xFFF) | ( ($fat->[$i+1] & 0xFFF) << 12 ); |
|
728
|
1957
|
|
|
|
|
2866
|
$buf .= pack 'vC', $v, ($v >> 16); |
|
729
|
|
|
|
|
|
|
} |
|
730
|
29
|
100
|
|
|
|
74
|
$buf .= pack 'v', $fat->[$max] & 0xFFF unless $max & 1; |
|
731
|
29
|
|
|
|
|
158
|
return $buf; |
|
732
|
|
|
|
|
|
|
} |
|
733
|
|
|
|
|
|
|
} |
|
734
|
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
# This calculates the encoded size of one directory |
|
736
|
65
|
|
|
65
|
|
77
|
sub _calc_dir_size($self, $dir) { |
|
|
65
|
|
|
|
|
81
|
|
|
|
65
|
|
|
|
|
85
|
|
|
|
65
|
|
|
|
|
69
|
|
|
737
|
|
|
|
|
|
|
# If an autovivified directoy lacks a ->{file}, create it. |
|
738
|
65
|
|
66
|
|
|
249
|
$dir->{file} //= Sys::Export::VFAT::File->new(name => $dir->name, flags => ATTR_DIRECTORY); |
|
739
|
65
|
|
|
|
|
153
|
my $ents= $dir->entries; |
|
740
|
|
|
|
|
|
|
# Need the 8.3 name in order to know whether it matches the long name |
|
741
|
65
|
|
|
|
|
176
|
$dir->build_shortnames; |
|
742
|
|
|
|
|
|
|
# root dir has a volume label ent, and all other dirs have '.' and '..' |
|
743
|
65
|
100
|
|
|
|
137
|
my $n= @$ents + ($dir->is_root? 1 : 2); |
|
744
|
65
|
|
|
|
|
114
|
for (@$ents) { |
|
745
|
|
|
|
|
|
|
# Add LFN entries |
|
746
|
10550
|
100
|
|
|
|
16784
|
if ($_->{name} ne $_->{shortname}) { |
|
747
|
36
|
|
|
|
|
174
|
my $utf16= encode('UTF-16LE', $_->{name}, Encode::FB_CROAK|Encode::LEAVE_SRC); |
|
748
|
36
|
|
|
|
|
3816
|
$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
|
|
|
|
193
|
[ map +( $_->{name} eq $_->{shortname}? $_->{name} : [ @{$_}{'name','shortname'} ] ), @$ents ]) |
|
|
0
|
50
|
|
|
|
0
|
|
|
754
|
|
|
|
|
|
|
if $log->is_debug; |
|
755
|
65
|
50
|
|
|
|
563
|
croak "Directory ".$dir->name." exceeds maximum entry count ($n >= 65536)" |
|
756
|
|
|
|
|
|
|
if $n >= 65536; |
|
757
|
65
|
|
|
|
|
148
|
$dir->file->{size}= $n * 32; # always 32 bytes per dirent |
|
758
|
|
|
|
|
|
|
} |
|
759
|
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
# Pack one directory and return the buffer |
|
761
|
65
|
|
|
65
|
|
71
|
sub _pack_directory($self, $dir) { |
|
|
65
|
|
|
|
|
65
|
|
|
|
65
|
|
|
|
|
82
|
|
|
|
65
|
|
|
|
|
60
|
|
|
762
|
65
|
|
|
|
|
84
|
my $data= ''; |
|
763
|
65
|
100
|
50
|
|
|
173
|
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
|
|
|
|
|
157
|
for my $ent (@special, sort { lc $a->{name} cmp lc $b->{name} } $dir->entries->@*) { |
|
|
22567
|
|
|
|
|
43695
|
|
|
770
|
10649
|
|
|
|
|
13364
|
my ($name, $file, $shortname, $short11)= @{$ent}{qw( name file shortname short11 )}; |
|
|
10649
|
|
|
|
|
25183
|
|
|
771
|
10649
|
|
66
|
|
|
24892
|
$log->tracef("encoding dirent short=%-12s long=%s cluster=%s", |
|
|
|
|
100
|
|
|
|
|
|
772
|
|
|
|
|
|
|
$shortname//$short11, $name, $file && $file->cluster); |
|
773
|
|
|
|
|
|
|
|
|
774
|
10649
|
100
|
|
|
|
62419
|
unless (length $short11) { |
|
775
|
10550
|
|
|
|
|
17443
|
my ($base, $ext)= split /\./, $shortname; |
|
776
|
10550
|
|
100
|
|
|
23303
|
$short11= pack 'A8 A3', $base, ($ext//''); |
|
777
|
|
|
|
|
|
|
} |
|
778
|
10649
|
|
|
|
|
15260
|
$short11 =~ s/^\xE5/\x05/; # \xE5 may occur in some charsets, and needs escaped |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
# Need Long-File-Name entries? |
|
781
|
10649
|
100
|
100
|
|
|
23899
|
if (defined $name && $name ne $shortname) { |
|
782
|
|
|
|
|
|
|
# Checksum for directory shortname, used to verify long name parts |
|
783
|
36
|
|
|
|
|
42
|
my $cksum= 0; |
|
784
|
|
|
|
|
|
|
$cksum= ((($cksum >> 1) | ($cksum << 7)) + $_) & 0xFF |
|
785
|
36
|
|
|
|
|
251
|
for unpack 'C*', $short11; |
|
786
|
|
|
|
|
|
|
# Each dirent holds up to 26 bytes (13 chars) of the long name |
|
787
|
36
|
|
|
|
|
230
|
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
|
|
|
|
643
|
if (my $remainder= @chars % 13) { |
|
790
|
36
|
|
|
|
|
47
|
push @chars, 0; |
|
791
|
36
|
|
|
|
|
81
|
push @chars, (0xFFFF)x(12 - $remainder); |
|
792
|
|
|
|
|
|
|
} |
|
793
|
36
|
|
|
|
|
108
|
my $last= ceil(@chars/13) - 1; |
|
794
|
36
|
|
|
|
|
76
|
for my $i (reverse 0..$last) { |
|
795
|
37
|
|
|
|
|
44
|
my $ofs= $i*13; |
|
796
|
37
|
100
|
|
|
|
64
|
my $seq= ($i + 1) | (($i == $last) ? 0x40 : 0x00); |
|
797
|
37
|
|
|
|
|
277
|
$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
|
|
|
23194
|
my $mtime= $ent->{mtime} // ($file && $file->mtime) // time; |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
809
|
10649
|
|
66
|
|
|
23025
|
my $atime= $ent->{atime} // ($file && $file->atime) // $mtime; |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
810
|
10649
|
|
66
|
|
|
23570
|
my $btime= $ent->{btime} // ($file && $file->btime) // $mtime; |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
811
|
10649
|
|
|
|
|
13864
|
my ($wdate, $wtime) = _epoch_to_fat_date_time($mtime); |
|
812
|
10649
|
|
|
|
|
13173
|
my ($cdate, $ctime, $ctime_frac)= _epoch_to_fat_date_time($btime); |
|
813
|
10649
|
|
|
|
|
15343
|
my ($adate) = _epoch_to_fat_date_time($atime); |
|
814
|
10649
|
|
33
|
|
|
29237
|
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
|
|
|
|
|
11140
|
my $cluster= 0; |
|
819
|
10649
|
100
|
100
|
|
|
20855
|
if ($file && $file != $self->root->file) { |
|
820
|
10614
|
|
33
|
|
|
16142
|
$cluster= $file->cluster // croak "File ".$file->name." lacks a defined cluster id"; |
|
821
|
|
|
|
|
|
|
} |
|
822
|
|
|
|
|
|
|
# Directories always written as size = 0 |
|
823
|
10649
|
100
|
|
|
|
20622
|
my $size= !$file? 0 : $file->is_dir? 0 : $file->size; |
|
|
|
100
|
|
|
|
|
|
|
824
|
10649
|
|
|
|
|
28570
|
$log->tracef(" with encoded size=%d cluster=%d", $size, $cluster); |
|
825
|
10649
|
|
|
|
|
92014
|
$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
|
|
|
|
131
|
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
|
|
|
|
|
137
|
$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__ |