| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sys::Export::VFAT::Geometry; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Calculate addresses and sizes of structures within a FAT filesystem |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.005'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
5
|
|
|
5
|
|
219671
|
use v5.26; |
|
|
5
|
|
|
|
|
14
|
|
|
8
|
5
|
|
|
5
|
|
19
|
use warnings; |
|
|
5
|
|
|
|
|
8
|
|
|
|
5
|
|
|
|
|
231
|
|
|
9
|
5
|
|
|
5
|
|
22
|
use experimental qw( signatures ); |
|
|
5
|
|
|
|
|
7
|
|
|
|
5
|
|
|
|
|
29
|
|
|
10
|
5
|
|
|
5
|
|
1056
|
use Sys::Export qw( isa_hash isa_int isa_pow2 round_up_to_pow2 round_up_to_multiple ); |
|
|
5
|
|
|
|
|
29
|
|
|
|
5
|
|
|
|
|
32
|
|
|
11
|
5
|
|
|
5
|
|
26
|
use Scalar::Util qw( dualvar ); |
|
|
5
|
|
|
|
|
8
|
|
|
|
5
|
|
|
|
|
224
|
|
|
12
|
5
|
|
|
5
|
|
1118
|
use POSIX 'ceil'; |
|
|
5
|
|
|
|
|
11369
|
|
|
|
5
|
|
|
|
|
32
|
|
|
13
|
5
|
|
|
5
|
|
2831
|
use Carp; |
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
525
|
|
|
14
|
|
|
|
|
|
|
our @CARP_NOT= qw( Sys::Export::VFAT ); |
|
15
|
|
|
|
|
|
|
use constant { |
|
16
|
5
|
|
|
|
|
759
|
FAT12 => dualvar(12, "FAT12"), |
|
17
|
|
|
|
|
|
|
FAT16 => dualvar(16, "FAT16"), |
|
18
|
|
|
|
|
|
|
FAT32 => dualvar(32, "FAT32"), |
|
19
|
|
|
|
|
|
|
FAT12_MAX_CLUSTERS => 4085-1, |
|
20
|
|
|
|
|
|
|
FAT12_IDEAL_MAX_CLUSTERS => 4085-16, # docs recommend 16 away from cutoff on either side |
|
21
|
|
|
|
|
|
|
FAT16_MIN_CLUSTERS => 4085, |
|
22
|
|
|
|
|
|
|
FAT16_IDEAL_MIN_CLUSTERS => 4085+16, |
|
23
|
|
|
|
|
|
|
FAT16_MAX_CLUSTERS => 65525-1, |
|
24
|
|
|
|
|
|
|
FAT16_IDEAL_MAX_CLUSTERS => 65525-16, |
|
25
|
|
|
|
|
|
|
FAT32_MIN_CLUSTERS => 65525, |
|
26
|
|
|
|
|
|
|
FAT32_IDEAL_MIN_CLUSTERS => 65525+16, |
|
27
|
|
|
|
|
|
|
FAT32_MAX_CLUSTERS => 0xFFFFFF5, # can't allow 0xFFFFFF7 to be allocatable ID |
|
28
|
5
|
|
|
5
|
|
20
|
}; |
|
|
5
|
|
|
|
|
8
|
|
|
29
|
5
|
|
|
5
|
|
21
|
use Exporter 'import'; |
|
|
5
|
|
|
|
|
8
|
|
|
|
5
|
|
|
|
|
13572
|
|
|
30
|
|
|
|
|
|
|
our @EXPORT_OK= qw( FAT12 FAT16 FAT32 FAT12_MAX_CLUSTERS FAT12_IDEAL_MAX_CLUSTERS |
|
31
|
|
|
|
|
|
|
FAT16_MIN_CLUSTERS FAT16_IDEAL_MIN_CLUSTERS FAT16_MAX_CLUSTERS FAT16_IDEAL_MAX_CLUSTERS |
|
32
|
|
|
|
|
|
|
FAT32_MIN_CLUSTERS FAT32_IDEAL_MIN_CLUSTERS FAT32_MAX_CLUSTERS ); |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
|
|
35
|
378
|
|
|
378
|
1
|
375476
|
sub new($class, @attrs) { |
|
|
378
|
|
|
|
|
547
|
|
|
|
378
|
|
|
|
|
1330
|
|
|
|
378
|
|
|
|
|
518
|
|
|
36
|
378
|
50
|
33
|
|
|
2315
|
my %attrs= @attrs == 1 && isa_hash $attrs[0]? %{$attrs[0]} : @attrs; |
|
|
0
|
|
|
|
|
0
|
|
|
37
|
|
|
|
|
|
|
my ($bytes_per_sector, $sectors_per_cluster, $fat_count, $reserved_sector_count, |
|
38
|
|
|
|
|
|
|
$fat_sector_count, $root_dirent_count, $cluster_count, $total_sector_count, |
|
39
|
|
|
|
|
|
|
$min_bits, $volume_offset, $align_clusters |
|
40
|
378
|
|
|
|
|
1977
|
) = delete @attrs{qw( |
|
41
|
|
|
|
|
|
|
bytes_per_sector sectors_per_cluster fat_count reserved_sector_count |
|
42
|
|
|
|
|
|
|
fat_sector_count root_dirent_count cluster_count total_sector_count |
|
43
|
|
|
|
|
|
|
min_bits volume_offset align_clusters |
|
44
|
|
|
|
|
|
|
)}; |
|
45
|
378
|
50
|
66
|
|
|
1175
|
!defined $align_clusters or isa_pow2($align_clusters) |
|
46
|
|
|
|
|
|
|
or croak "align_clusters must be a power of 2 (was $align_clusters)"; |
|
47
|
378
|
|
100
|
|
|
787
|
$volume_offset //= 0; |
|
48
|
378
|
50
|
33
|
|
|
884
|
isa_int($volume_offset) && $volume_offset >= 0 |
|
49
|
|
|
|
|
|
|
or croak "volume_offset must be a non-negative integer"; |
|
50
|
|
|
|
|
|
|
|
|
51
|
378
|
|
100
|
|
|
636
|
$bytes_per_sector //= 512; |
|
52
|
378
|
50
|
33
|
|
|
708
|
isa_pow2($bytes_per_sector) && 512 <= $bytes_per_sector && $bytes_per_sector <= 4096 |
|
|
|
|
33
|
|
|
|
|
|
53
|
|
|
|
|
|
|
or croak "Invalid bytes_per_sector $bytes_per_sector"; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# Default sectors_per_cluster to whatever makes 4K |
|
56
|
378
|
50
|
66
|
|
|
680
|
$sectors_per_cluster //= ($bytes_per_sector >= 4096? 1 : 4096 / $bytes_per_sector); |
|
57
|
378
|
50
|
33
|
|
|
533
|
isa_pow2($sectors_per_cluster) && $sectors_per_cluster <= 128 |
|
58
|
|
|
|
|
|
|
or croak "Invalid sectors_per_cluster $sectors_per_cluster"; |
|
59
|
378
|
|
|
|
|
573
|
my $cluster_size= $bytes_per_sector * $sectors_per_cluster; |
|
60
|
378
|
50
|
|
|
|
602
|
$cluster_size <= 32*1024 |
|
61
|
|
|
|
|
|
|
or carp "Warning: bytes_per_sector * sectors_per_cluster > 32KiB which is not valid for some drivers"; |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# Default fat_count to 2 unless specified otherwise |
|
64
|
378
|
|
100
|
|
|
876
|
$fat_count //= 2; |
|
65
|
378
|
50
|
33
|
|
|
515
|
isa_int $fat_count && 0 < $fat_count && $fat_count <= 255 |
|
|
|
|
33
|
|
|
|
|
|
66
|
|
|
|
|
|
|
or croak "Invalid fat_count $fat_count"; |
|
67
|
|
|
|
|
|
|
|
|
68
|
378
|
|
|
|
|
1280
|
my $self= bless { |
|
69
|
|
|
|
|
|
|
bytes_per_sector => $bytes_per_sector, |
|
70
|
|
|
|
|
|
|
sectors_per_cluster => $sectors_per_cluster, |
|
71
|
|
|
|
|
|
|
fat_count => $fat_count, |
|
72
|
|
|
|
|
|
|
volume_offset => $volume_offset, |
|
73
|
|
|
|
|
|
|
}; |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# From here down, we are either determining cluster_count from other properties, |
|
76
|
|
|
|
|
|
|
# or deriving other properties from cluster_count. |
|
77
|
378
|
|
|
|
|
549
|
my $bits; |
|
78
|
378
|
50
|
33
|
|
|
994
|
if (defined $reserved_sector_count && defined $fat_sector_count |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
79
|
|
|
|
|
|
|
&& defined $root_dirent_count && defined $total_sector_count |
|
80
|
|
|
|
|
|
|
) { |
|
81
|
|
|
|
|
|
|
# All main properties of the geometry are defined. |
|
82
|
0
|
|
|
|
|
0
|
my $root_sector_count= int(($root_dirent_count + ($self->dirent_per_sector-1)) / $self->dirent_per_sector); |
|
83
|
0
|
|
|
|
|
0
|
my $data_sectors= $total_sector_count - $reserved_sector_count - $fat_count * $fat_sector_count - $root_sector_count; |
|
84
|
0
|
|
|
|
|
0
|
my $calc_cluster_count= int($data_sectors / $sectors_per_cluster); |
|
85
|
0
|
0
|
0
|
|
|
0
|
croak "Supplied cluster_count disagrees with computed value" |
|
86
|
|
|
|
|
|
|
if defined $cluster_count && $cluster_count != $calc_cluster_count; |
|
87
|
0
|
|
0
|
|
|
0
|
$cluster_count //= $calc_cluster_count; |
|
88
|
0
|
0
|
|
|
|
0
|
$bits= $cluster_count < FAT16_MIN_CLUSTERS? FAT12 |
|
|
|
0
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
: $cluster_count < FAT32_MIN_CLUSTERS? FAT16 |
|
90
|
|
|
|
|
|
|
: FAT32; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
elsif (defined $cluster_count) { |
|
93
|
378
|
50
|
33
|
|
|
561
|
isa_int $cluster_count && $cluster_count > 0 |
|
94
|
|
|
|
|
|
|
or croak "Invalid cluster_count '$cluster_count'"; |
|
95
|
|
|
|
|
|
|
# FAT docs recommend avoiding numbers near the boundary of FAT12/FAT16/FAT32 to avoid |
|
96
|
|
|
|
|
|
|
# other people's math errors. But, allow the caller to disable this adjustment. |
|
97
|
378
|
50
|
|
|
|
752
|
unless (delete $attrs{exact_cluster_count}) { |
|
98
|
378
|
50
|
66
|
|
|
1246
|
if ($cluster_count >= FAT12_IDEAL_MAX_CLUSTERS && $cluster_count < FAT16_IDEAL_MIN_CLUSTERS) { |
|
|
|
50
|
66
|
|
|
|
|
|
99
|
0
|
|
|
|
|
0
|
$cluster_count= FAT16_IDEAL_MIN_CLUSTERS; |
|
100
|
|
|
|
|
|
|
} elsif ($cluster_count >= FAT16_IDEAL_MAX_CLUSTERS && $cluster_count < FAT32_IDEAL_MIN_CLUSTERS) { |
|
101
|
0
|
|
|
|
|
0
|
$cluster_count= FAT32_IDEAL_MIN_CLUSTERS; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
# These are the official boundary numbers that determine the filesystem type |
|
105
|
378
|
|
50
|
|
|
1097
|
$min_bits //= FAT12; |
|
106
|
378
|
100
|
|
|
|
729
|
$bits= $cluster_count < FAT16_MIN_CLUSTERS? FAT12 |
|
|
|
100
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
: $cluster_count < FAT32_MIN_CLUSTERS? FAT16 |
|
108
|
|
|
|
|
|
|
: FAT32; |
|
109
|
378
|
50
|
|
|
|
597
|
if ($bits < $min_bits) { |
|
110
|
0
|
|
|
|
|
0
|
$bits= $min_bits; |
|
111
|
|
|
|
|
|
|
# Increase to the minimum number of clusters if a specific number of bits |
|
112
|
|
|
|
|
|
|
# was requested. |
|
113
|
0
|
0
|
|
|
|
0
|
$cluster_count= ($bits == FAT16)? FAT16_IDEAL_MIN_CLUSTERS : FAT32_IDEAL_MIN_CLUSTERS; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
else { |
|
117
|
0
|
|
|
|
|
0
|
croak "Not enough attributes supplied to determine geometry"; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
378
|
|
|
|
|
615
|
$self->{cluster_count}= $cluster_count; |
|
120
|
378
|
|
|
|
|
894
|
$self->{bits}= $bits; |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# Check how many sectors are occupied by each allocation table |
|
123
|
378
|
|
|
|
|
595
|
my $fat_byte_count= ( ($cluster_count + 2) * $bits + 7 ) >> 3; # round up to bytes |
|
124
|
378
|
50
|
|
|
|
614
|
if (defined $fat_sector_count) { |
|
125
|
0
|
0
|
|
|
|
0
|
$fat_sector_count * $bytes_per_sector >= $fat_byte_count |
|
126
|
|
|
|
|
|
|
or croak "Invalid fat_sector_count, smaller than $fat_byte_count bytes"; |
|
127
|
|
|
|
|
|
|
} else { |
|
128
|
378
|
|
|
|
|
731
|
$fat_sector_count= int(($fat_byte_count + ($bytes_per_sector - 1)) / $bytes_per_sector); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
378
|
|
|
|
|
564
|
$self->{fat_sector_count}= $fat_sector_count; |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Check how many sectors are occupied by root directory entries |
|
133
|
|
|
|
|
|
|
# For fat12/16, The FAT spec document suggests 512 as a good default |
|
134
|
|
|
|
|
|
|
# Allow the user to supply the actual number of root entries and then we round that. |
|
135
|
378
|
|
|
|
|
535
|
my $used_root_dirent_count= delete $attrs{used_root_dirent_count}; |
|
136
|
378
|
100
|
|
|
|
673
|
if ($bits < FAT32) { |
|
137
|
337
|
100
|
|
|
|
464
|
if (defined $root_dirent_count) { |
|
138
|
1
|
50
|
33
|
|
|
6
|
$root_dirent_count >= 1 && $root_dirent_count < 0xFFFF |
|
139
|
|
|
|
|
|
|
or croak "Invalid root_dirent_count for FAT12/16"; |
|
140
|
|
|
|
|
|
|
} else { |
|
141
|
336
|
|
100
|
|
|
676
|
$root_dirent_count= $used_root_dirent_count // 512; |
|
142
|
|
|
|
|
|
|
# Round up to as many as fit in this number of sectors |
|
143
|
336
|
|
|
|
|
611
|
my $remainder= ($root_dirent_count & ($self->dirent_per_sector - 1)); |
|
144
|
336
|
100
|
|
|
|
728
|
$root_dirent_count += ($self->dirent_per_sector - $remainder) |
|
145
|
|
|
|
|
|
|
if $remainder; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
337
|
50
|
50
|
|
|
940
|
($reserved_sector_count //= 1) == 1 |
|
149
|
|
|
|
|
|
|
or croak "reserved_sector_count should be 1 for FAT12/16"; |
|
150
|
|
|
|
|
|
|
} else { |
|
151
|
41
|
50
|
50
|
|
|
195
|
($root_dirent_count //= 0) == 0 |
|
152
|
|
|
|
|
|
|
or croak "root_dirent_count must be zero for FAT32"; |
|
153
|
|
|
|
|
|
|
|
|
154
|
41
|
|
50
|
|
|
150
|
$reserved_sector_count //= 32; |
|
155
|
41
|
50
|
33
|
|
|
89
|
isa_int $reserved_sector_count && $reserved_sector_count >= 2 |
|
156
|
|
|
|
|
|
|
or croak "reserved_sector_count must be greater than 2 for FAT32"; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# If caller requested alignment of clusters, figure that out |
|
160
|
378
|
100
|
100
|
|
|
902
|
if (defined $align_clusters && $align_clusters > $bytes_per_sector) { |
|
161
|
|
|
|
|
|
|
# there's a method for this, but avoid caching things yet |
|
162
|
220
|
|
|
|
|
403
|
my $data_addr= $volume_offset + $bytes_per_sector * ( |
|
163
|
|
|
|
|
|
|
$reserved_sector_count |
|
164
|
|
|
|
|
|
|
+ ($fat_count*$fat_sector_count) |
|
165
|
|
|
|
|
|
|
+ ceil($root_dirent_count / $self->dirent_per_sector) |
|
166
|
|
|
|
|
|
|
); |
|
167
|
|
|
|
|
|
|
# If the cluster size is greater or equal to the requested alignment, ensure the |
|
168
|
|
|
|
|
|
|
# data start falls on that boundary. |
|
169
|
|
|
|
|
|
|
# If the cluster size is smaller than the requested alignment, ensure the data start |
|
170
|
|
|
|
|
|
|
# falls on a cluster boundary so that some number of clusters will equal the alignment. |
|
171
|
220
|
100
|
|
|
|
427
|
my $align= ($cluster_size >= $align_clusters)? $align_clusters : $cluster_size; |
|
172
|
220
|
100
|
|
|
|
465
|
if (my $ofs= $data_addr & ($align-1)) { |
|
173
|
162
|
|
|
|
|
254
|
my $shift_n_sectors= ($align - $ofs) / $bytes_per_sector; |
|
174
|
|
|
|
|
|
|
#say sprintf "# remainder=0x%X, cluster_size=$cluster_size align_clusters=$align_clusters $align-$ofs=".($align-$ofs)." shift %d sectors", $ofs, $shift_n_sectors; |
|
175
|
162
|
100
|
|
|
|
299
|
if ($bits < FAT32) { |
|
176
|
|
|
|
|
|
|
# Reserved sectors should be 1, so expand number of root entries |
|
177
|
149
|
|
|
|
|
289
|
$root_dirent_count += $shift_n_sectors * $self->dirent_per_sector; |
|
178
|
|
|
|
|
|
|
} else { |
|
179
|
|
|
|
|
|
|
# Add however many reserved sectors we need |
|
180
|
13
|
|
|
|
|
28
|
$reserved_sector_count += $shift_n_sectors; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
} |
|
184
|
378
|
|
|
|
|
527
|
$self->{root_dirent_count}= $root_dirent_count; |
|
185
|
378
|
|
|
|
|
675
|
$self->{reserved_sector_count}= $reserved_sector_count; |
|
186
|
|
|
|
|
|
|
|
|
187
|
378
|
50
|
|
|
|
552
|
carp "Unused constructor parameters: ".join(' ', keys %attrs) |
|
188
|
|
|
|
|
|
|
if keys %attrs; |
|
189
|
378
|
|
|
|
|
1390
|
$self; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
|
|
193
|
23345
|
|
|
23345
|
1
|
22408
|
sub volume_offset($self) { $self->{volume_offset} } |
|
|
23345
|
|
|
|
|
21902
|
|
|
|
23345
|
|
|
|
|
22462
|
|
|
|
23345
|
|
|
|
|
38619
|
|
|
194
|
|
|
|
|
|
|
|
|
195
|
23587
|
|
|
23587
|
1
|
25051
|
sub bytes_per_sector($self) { $self->{bytes_per_sector} } |
|
|
23587
|
|
|
|
|
23658
|
|
|
|
23587
|
|
|
|
|
21524
|
|
|
|
23587
|
|
|
|
|
64483
|
|
|
196
|
22073
|
|
|
22073
|
1
|
22088
|
sub sectors_per_cluster($self) { $self->{sectors_per_cluster} } |
|
|
22073
|
|
|
|
|
21926
|
|
|
|
22073
|
|
|
|
|
19873
|
|
|
|
22073
|
|
|
|
|
44364
|
|
|
197
|
31338
|
|
|
31338
|
1
|
58509
|
sub bytes_per_cluster($self) { $self->{bytes_per_sector} * $self->{sectors_per_cluster} } |
|
|
31338
|
|
|
|
|
29717
|
|
|
|
31338
|
|
|
|
|
28548
|
|
|
|
31338
|
|
|
|
|
57375
|
|
|
198
|
1513
|
|
|
1513
|
1
|
1724
|
sub dirent_per_sector($self) { $self->{bytes_per_sector} / 32 } |
|
|
1513
|
|
|
|
|
1555
|
|
|
|
1513
|
|
|
|
|
1481
|
|
|
|
1513
|
|
|
|
|
5188
|
|
|
199
|
1
|
|
|
1
|
1
|
127
|
sub dirent_per_cluster($self) { $self->{bytes_per_sector} * $self->{sectors_per_cluster} / 32 } |
|
|
1
|
|
|
|
|
17
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
4
|
|
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
|
|
202
|
406
|
|
|
406
|
1
|
2863
|
sub bits($self) { $self->{bits} } |
|
|
406
|
|
|
|
|
402
|
|
|
|
406
|
|
|
|
|
389
|
|
|
|
406
|
|
|
|
|
1307
|
|
|
203
|
530
|
|
|
530
|
1
|
787
|
sub reserved_sector_count($self) { $self->{reserved_sector_count} } |
|
|
530
|
|
|
|
|
581
|
|
|
|
530
|
|
|
|
|
558
|
|
|
|
530
|
|
|
|
|
1072
|
|
|
204
|
62
|
|
|
62
|
1
|
65
|
sub reserved_size($self) { $self->{reserved_sector_count} * $self->bytes_per_sector } |
|
|
62
|
|
|
|
|
61
|
|
|
|
62
|
|
|
|
|
60
|
|
|
|
62
|
|
|
|
|
102
|
|
|
205
|
503
|
|
|
503
|
1
|
701
|
sub fat_count($self) { $self->{fat_count} } |
|
|
503
|
|
|
|
|
489
|
|
|
|
503
|
|
|
|
|
440
|
|
|
|
503
|
|
|
|
|
932
|
|
|
206
|
590
|
|
|
590
|
1
|
766
|
sub fat_sector_count($self) { $self->{fat_sector_count} } |
|
|
590
|
|
|
|
|
744
|
|
|
|
590
|
|
|
|
|
590
|
|
|
|
590
|
|
|
|
|
1398
|
|
|
207
|
124
|
|
|
124
|
1
|
116
|
sub fat_size($self) { $self->{fat_sector_count} * $self->bytes_per_sector } |
|
|
124
|
|
|
|
|
130
|
|
|
|
124
|
|
|
|
|
117
|
|
|
|
124
|
|
|
|
|
187
|
|
|
208
|
22592
|
|
|
22592
|
1
|
22153
|
sub cluster_count($self) { $self->{cluster_count} } |
|
|
22592
|
|
|
|
|
21234
|
|
|
|
22592
|
|
|
|
|
20066
|
|
|
|
22592
|
|
|
|
|
40978
|
|
|
209
|
0
|
|
|
0
|
1
|
0
|
sub min_cluster_id($self) { 2 } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
210
|
22112
|
|
|
22112
|
1
|
20970
|
sub max_cluster_id($self) { $self->cluster_count + 1 } |
|
|
22112
|
|
|
|
|
20334
|
|
|
|
22112
|
|
|
|
|
19564
|
|
|
|
22112
|
|
|
|
|
26831
|
|
|
211
|
591
|
|
|
591
|
1
|
736
|
sub root_dirent_count($self) { $self->{root_dirent_count} } |
|
|
591
|
|
|
|
|
598
|
|
|
|
591
|
|
|
|
|
641
|
|
|
|
591
|
|
|
|
|
1152
|
|
|
212
|
559
|
|
|
559
|
1
|
751
|
sub root_dir_sector_count($self) { ceil($self->root_dirent_count / $self->dirent_per_sector) } |
|
|
559
|
|
|
|
|
596
|
|
|
|
559
|
|
|
|
|
622
|
|
|
|
559
|
|
|
|
|
835
|
|
|
213
|
60
|
|
|
60
|
1
|
62
|
sub root_dir_size($self) { $self->root_dir_sector_count * $self->bytes_per_sector } |
|
|
60
|
|
|
|
|
68
|
|
|
|
60
|
|
|
|
|
72
|
|
|
|
60
|
|
|
|
|
83
|
|
|
214
|
|
|
|
|
|
|
|
|
215
|
378
|
|
|
378
|
1
|
349
|
sub root_dir_start_sector($self) { |
|
|
378
|
|
|
|
|
362
|
|
|
|
378
|
|
|
|
|
372
|
|
|
216
|
378
|
|
|
|
|
620
|
$self->reserved_sector_count + $self->fat_count * $self->fat_sector_count |
|
217
|
|
|
|
|
|
|
} |
|
218
|
0
|
|
|
0
|
1
|
0
|
sub root_dir_offset($self) { $self->root_dir_start_sector * $self->bytes_per_sector } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
219
|
23539
|
|
|
23539
|
1
|
22482
|
sub data_start_sector($self) { |
|
|
23539
|
|
|
|
|
22607
|
|
|
|
23539
|
|
|
|
|
21638
|
|
|
220
|
23539
|
|
66
|
|
|
46973
|
$self->{data_start_sector} //= $self->root_dir_start_sector + $self->root_dir_sector_count; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
270
|
|
|
270
|
1
|
262
|
sub data_limit_sector($self) { |
|
|
270
|
|
|
|
|
312
|
|
|
|
270
|
|
|
|
|
303
|
|
|
223
|
270
|
|
|
|
|
373
|
$self->data_start_sector + $self->cluster_count * $self->sectors_per_cluster |
|
224
|
|
|
|
|
|
|
} |
|
225
|
1370
|
|
|
1370
|
1
|
1576
|
sub data_start_offset($self) { $self->data_start_sector * $self->bytes_per_sector } |
|
|
1370
|
|
|
|
|
1510
|
|
|
|
1370
|
|
|
|
|
1407
|
|
|
|
1370
|
|
|
|
|
2053
|
|
|
226
|
270
|
|
|
270
|
1
|
262
|
sub data_limit_offset($self) { $self->data_limit_sector * $self->bytes_per_sector } |
|
|
270
|
|
|
|
|
247
|
|
|
|
270
|
|
|
|
|
250
|
|
|
|
270
|
|
|
|
|
388
|
|
|
227
|
1369
|
|
|
1369
|
1
|
1872
|
sub data_start_device_offset($self) { $self->volume_offset + $self->data_start_offset } |
|
|
1369
|
|
|
|
|
1596
|
|
|
|
1369
|
|
|
|
|
1453
|
|
|
|
1369
|
|
|
|
|
2193
|
|
|
228
|
270
|
|
|
270
|
1
|
315
|
sub data_limit_device_offset($self) { $self->volume_offset + $self->data_limit_offset } |
|
|
270
|
|
|
|
|
281
|
|
|
|
270
|
|
|
|
|
210
|
|
|
|
270
|
|
|
|
|
343
|
|
|
229
|
|
|
|
|
|
|
|
|
230
|
0
|
|
|
0
|
1
|
0
|
sub data_sector_count($self) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
231
|
0
|
|
|
|
|
0
|
$self->total_sector_count - $self->data_start_sector; |
|
232
|
|
|
|
|
|
|
} |
|
233
|
|
|
|
|
|
|
|
|
234
|
526
|
|
|
526
|
1
|
624
|
sub total_sector_count($self) { |
|
|
526
|
|
|
|
|
474
|
|
|
|
526
|
|
|
|
|
454
|
|
|
235
|
526
|
|
66
|
|
|
1545
|
$self->{total_sector_count} //= $self->data_start_sector |
|
236
|
|
|
|
|
|
|
+ $self->cluster_count * $self->sectors_per_cluster; |
|
237
|
|
|
|
|
|
|
} |
|
238
|
98
|
|
|
98
|
1
|
121
|
sub total_size($self) { $self->total_sector_count * $self->bytes_per_sector } |
|
|
98
|
|
|
|
|
99
|
|
|
|
98
|
|
|
|
|
103
|
|
|
|
98
|
|
|
|
|
145
|
|
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
|
|
241
|
21564
|
|
|
21564
|
1
|
20047
|
sub get_cluster_start_sector($self, $cluster_id) { |
|
|
21564
|
|
|
|
|
21616
|
|
|
|
21564
|
|
|
|
|
20684
|
|
|
|
21564
|
|
|
|
|
21169
|
|
|
242
|
21564
|
50
|
|
|
|
28267
|
croak "Cluster 0 and 1 are reserved" if $cluster_id < 2; |
|
243
|
21564
|
50
|
|
|
|
27083
|
croak "Cluster $cluster_id beyond end of volume" if $cluster_id > $self->max_cluster_id; |
|
244
|
21564
|
|
|
|
|
29280
|
return $self->data_start_sector + ($cluster_id-2) * $self->sectors_per_cluster; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
21564
|
|
|
21564
|
1
|
22458
|
sub get_cluster_offset($self, $cluster_id) { |
|
|
21564
|
|
|
|
|
22161
|
|
|
|
21564
|
|
|
|
|
20403
|
|
|
|
21564
|
|
|
|
|
19068
|
|
|
247
|
21564
|
|
|
|
|
26672
|
$self->get_cluster_start_sector($cluster_id) * $self->bytes_per_sector; |
|
248
|
|
|
|
|
|
|
} |
|
249
|
21557
|
|
|
21557
|
1
|
133996
|
sub get_cluster_device_offset($self, $cluster_id) { |
|
|
21557
|
|
|
|
|
20481
|
|
|
|
21557
|
|
|
|
|
21471
|
|
|
|
21557
|
|
|
|
|
20607
|
|
|
250
|
21557
|
|
|
|
|
33718
|
$self->volume_offset + $self->get_cluster_offset($cluster_id); |
|
251
|
|
|
|
|
|
|
} |
|
252
|
|
|
|
|
|
|
|
|
253
|
7
|
|
|
7
|
1
|
10
|
sub get_cluster_of_sector($self, $sector_idx) { |
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
26
|
|
|
254
|
7
|
50
|
|
|
|
13
|
return undef if $sector_idx < $self->data_start_sector; |
|
255
|
7
|
|
|
|
|
11
|
my $cluster= int(($sector_idx - $self->data_start_sector) / $self->sectors_per_cluster); |
|
256
|
7
|
50
|
|
|
|
14
|
return undef if $cluster >= $self->cluster_count; |
|
257
|
7
|
|
|
|
|
12
|
return $cluster + 2; |
|
258
|
|
|
|
|
|
|
} |
|
259
|
7
|
|
|
7
|
1
|
9
|
sub get_cluster_of_offset($self, $offset) { |
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
11
|
|
|
260
|
7
|
|
|
|
|
12
|
$self->get_cluster_of_sector(int($offset / $self->bytes_per_sector)); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
0
|
|
|
0
|
1
|
0
|
sub get_cluster_of_device_offset($self, $addr) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
263
|
0
|
|
|
|
|
0
|
$self->get_cluster_of_offset($addr - $self->volume_offset); |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
|
|
267
|
7
|
|
|
7
|
1
|
12
|
sub get_cluster_extent_of_volume_extent($self, $offset, $size) { |
|
|
7
|
|
|
|
|
9
|
|
|
|
7
|
|
|
|
|
10
|
|
|
|
7
|
|
|
|
|
9
|
|
|
|
7
|
|
|
|
|
9
|
|
|
268
|
7
|
|
|
|
|
12
|
my $cl_start= $self->get_cluster_of_offset($offset); |
|
269
|
7
|
50
|
|
|
|
14
|
$cl_start |
|
270
|
|
|
|
|
|
|
or croak "Offset $offset falls outside of cluster data region"; |
|
271
|
7
|
50
|
|
|
|
11
|
$self->get_cluster_offset($cl_start) == $offset |
|
272
|
|
|
|
|
|
|
or croak "FAT_offset not aligned to a cluster boundary"; |
|
273
|
7
|
|
|
|
|
13
|
my $cl_cnt= ceil($size / $self->bytes_per_cluster); |
|
274
|
7
|
50
|
|
|
|
15
|
$cl_start + $cl_cnt <= $self->max_cluster_id+1 |
|
275
|
|
|
|
|
|
|
or croak "byte range ($offset, $size) exceeds final cluster of volume"; |
|
276
|
7
|
|
|
|
|
20
|
return ($cl_start, $cl_cnt); |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
|
|
280
|
7
|
|
|
7
|
1
|
10
|
sub get_cluster_extent_of_device_extent($self, $addr, $size) { |
|
|
7
|
|
|
|
|
10
|
|
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
7
|
|
|
|
7
|
|
|
|
|
8
|
|
|
281
|
7
|
|
|
|
|
15
|
$self->get_cluster_extent_of_volume_extent($addr - $self->volume_offset, $size); |
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
|
|
285
|
587
|
|
|
587
|
1
|
58222
|
sub get_cluster_alignment_of_device_alignment($self, $align) { |
|
|
587
|
|
|
|
|
948
|
|
|
|
587
|
|
|
|
|
794
|
|
|
|
587
|
|
|
|
|
706
|
|
|
286
|
587
|
|
|
|
|
1085
|
my $cluster_size= $self->bytes_per_cluster; |
|
287
|
|
|
|
|
|
|
# If the cluster size is greater or equal to the requested alignment, verify that the |
|
288
|
|
|
|
|
|
|
# data start (plus the volume offset that was implicitly added to every volume address) |
|
289
|
|
|
|
|
|
|
# meets that alignment. If so, every cluster is aligned and the return value is (1,0) |
|
290
|
587
|
100
|
|
|
|
1449
|
if ($cluster_size >= $align) { |
|
291
|
262
|
50
|
|
|
|
495
|
croak "Clusters are not aligned to $align" |
|
292
|
|
|
|
|
|
|
if $self->data_start_device_offset & ($align-1); |
|
293
|
262
|
|
|
|
|
739
|
return (1,0); |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
# Otherwise, make sure the data_start (plus implied volume offset) is aligned to |
|
296
|
|
|
|
|
|
|
# cluster_size, so then some multiple of clusters will reach the requested alignment. |
|
297
|
325
|
50
|
|
|
|
641
|
croak "Clusters are not aligned to $cluster_size" |
|
298
|
|
|
|
|
|
|
if $self->data_start_device_offset & ($cluster_size-1); |
|
299
|
|
|
|
|
|
|
# The cluster alignment will be whatever multiple of clusters equals the byte alignment. |
|
300
|
|
|
|
|
|
|
# This will be at least 2. |
|
301
|
325
|
|
|
|
|
548
|
my $cl_align= $align / $cluster_size; |
|
302
|
|
|
|
|
|
|
# How many bytes away from alignment is the beginning of ficticious cluster 0? |
|
303
|
325
|
|
|
|
|
477
|
my $ofs_of_cl0= ($self->data_start_device_offset - ($cluster_size*2)) & ($align-1); |
|
304
|
|
|
|
|
|
|
# If not aligned, add the rest of the distance to the next alignment. |
|
305
|
325
|
100
|
|
|
|
692
|
my $cl_ofs= !$ofs_of_cl0? 0 : ($align - $ofs_of_cl0) / $cluster_size; |
|
306
|
325
|
|
|
|
|
780
|
return ($cl_align, $cl_ofs); |
|
307
|
|
|
|
|
|
|
} |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub unpack { |
|
311
|
0
|
|
|
0
|
1
|
|
my $class= shift; |
|
312
|
0
|
0
|
|
|
|
|
my $buf_ref= ref $_[0] eq 'SCALAR'? $_[0] : \$_[0]; |
|
313
|
0
|
0
|
|
|
|
|
my %attrs= ref $_[1] eq 'HASH'? %{$_[1]} : @_[1..$#_]; |
|
|
0
|
|
|
|
|
|
|
|
314
|
0
|
0
|
|
|
|
|
length($$buf_ref) >= 512 or croak "Pass at least the entire first sector to 'unpack'"; |
|
315
|
|
|
|
|
|
|
# According to the official spec, the only way to know whether you have FAT32 or FAT16 |
|
316
|
|
|
|
|
|
|
# is to calculate the count of clusters available in the data region, which this module |
|
317
|
|
|
|
|
|
|
# implements in the constructor. However, in order to unpack all of the fields of |
|
318
|
|
|
|
|
|
|
# sector0, you have to know whether it is FAT16 or FAT32 because FAT32 moves some of the |
|
319
|
|
|
|
|
|
|
# fields. But you need those extended fields to calculate whether it is FAT32 or not... |
|
320
|
|
|
|
|
|
|
# It's sort of a bullshit circular dependency when clearly you could use the |
|
321
|
|
|
|
|
|
|
# BPB_RootEntCnt to know whether it was FAT32 or not, since FAT32 will *always* be 0 and |
|
322
|
|
|
|
|
|
|
# the previous generations *can't* be 0. |
|
323
|
|
|
|
|
|
|
# Anyway, instead of uniform single-pass field unpacking, we get this: |
|
324
|
0
|
|
|
|
|
|
state %fields= qw( |
|
325
|
|
|
|
|
|
|
bytes_per_sector @11v |
|
326
|
|
|
|
|
|
|
sectors_per_cluster @13C |
|
327
|
|
|
|
|
|
|
reserved_sector_count @14v |
|
328
|
|
|
|
|
|
|
fat_count @16C |
|
329
|
|
|
|
|
|
|
root_dirent_count @17v |
|
330
|
|
|
|
|
|
|
fat_sector_count @22v |
|
331
|
|
|
|
|
|
|
fat_sector_count32 @36V |
|
332
|
|
|
|
|
|
|
total_sector_count @19v |
|
333
|
|
|
|
|
|
|
total_sector_count32 @32V |
|
334
|
|
|
|
|
|
|
); |
|
335
|
0
|
|
|
|
|
|
state @fields= keys %fields; |
|
336
|
0
|
|
|
|
|
|
state $packstr= join ' ', values %fields; |
|
337
|
|
|
|
|
|
|
|
|
338
|
0
|
|
|
|
|
|
@attrs{@fields}= unpack $packstr, $$buf_ref; |
|
339
|
0
|
|
|
|
|
|
for (qw( fat_sector_count total_sector_count )) { |
|
340
|
0
|
|
|
|
|
|
my $_32= delete $attrs{$_.'32'}; |
|
341
|
0
|
|
0
|
|
|
|
$attrs{$_} ||= $_32; |
|
342
|
|
|
|
|
|
|
} |
|
343
|
0
|
|
|
|
|
|
return $class->new(%attrs); |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# Avoiding dependency on namespace::clean |
|
347
|
|
|
|
|
|
|
delete @{Sys::Export::VFAT::Geometry::}{qw( |
|
348
|
|
|
|
|
|
|
carp confess croak ceil dualvar |
|
349
|
|
|
|
|
|
|
isa_hash isa_int isa_pow2 round_up_to_multiple round_up_to_pow2 |
|
350
|
|
|
|
|
|
|
)}; |
|
351
|
|
|
|
|
|
|
1; |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
__END__ |