| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sys::Export::VFAT::AllocationTable; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Track which FAT clusters are used, and by what |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.005'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
# Element 0 of the FAT is used for an inversion list of which sectors are allocated. |
|
8
|
|
|
|
|
|
|
# It's not as good as a tree, but should perform well when the typical use case is |
|
9
|
|
|
|
|
|
|
# to pack files end to end without fragmentation. |
|
10
|
4
|
|
|
4
|
|
217585
|
use v5.26; |
|
|
4
|
|
|
|
|
14
|
|
|
11
|
4
|
|
|
4
|
|
16
|
use warnings; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
200
|
|
|
12
|
4
|
|
|
4
|
|
20
|
use experimental qw( signatures ); |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
27
|
|
|
13
|
4
|
|
|
4
|
|
493
|
use Scalar::Util 'refaddr'; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
266
|
|
|
14
|
4
|
|
|
4
|
|
20
|
use Carp; |
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
176
|
|
|
15
|
4
|
|
|
4
|
|
591
|
use Sys::Export qw( isa_int ); |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
22
|
|
|
16
|
4
|
|
|
4
|
|
685
|
use Sys::Export::VFAT::Geometry qw( FAT12_MAX_CLUSTERS FAT16_MAX_CLUSTERS FAT32_MAX_CLUSTERS ); |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
7723
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
33
|
|
|
33
|
1
|
5561
|
sub fat { $_[0]{fat} } |
|
20
|
0
|
|
|
0
|
|
0
|
sub _invlist { $_[0]{_invlist} } |
|
21
|
10613
|
|
|
10613
|
1
|
37174
|
sub chains { $_[0]{chains} } |
|
22
|
634
|
100
|
|
634
|
1
|
1699
|
sub max_cluster_id { @_ > 1? $_[0]->set_max_cluster_id($_[1]) : $_[0]{max_cluster_id} } |
|
23
|
506
|
|
|
506
|
1
|
973
|
sub max_used_cluster_id { $_[0]{_invlist}[-1] - 1 } |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub first_free_cluster { |
|
27
|
5
|
|
|
5
|
1
|
15
|
my ($first_free, $max)= ($_[0]{_invlist}[1], $_[0]{max_cluster_id}); |
|
28
|
5
|
100
|
66
|
|
|
26
|
defined $max && $first_free > $max? undef : $first_free; |
|
29
|
|
|
|
|
|
|
} |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
|
32
|
202
|
|
|
202
|
1
|
227
|
sub set_max_cluster_id($self, $val) { |
|
|
202
|
|
|
|
|
192
|
|
|
|
202
|
|
|
|
|
211
|
|
|
|
202
|
|
|
|
|
221
|
|
|
33
|
202
|
100
|
|
|
|
330
|
if (defined $val) { |
|
34
|
201
|
|
|
|
|
266
|
my $min= $self->max_used_cluster_id; |
|
35
|
201
|
50
|
33
|
|
|
505
|
croak "Cannot set max_cluster_id less than max_used_cluster_id" |
|
36
|
|
|
|
|
|
|
if defined $min && $val < $min; |
|
37
|
201
|
50
|
|
|
|
312
|
croak "Cannot set max_cluster_id less than 2" |
|
38
|
|
|
|
|
|
|
if $val < 2; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
202
|
|
|
|
|
345
|
$self->{max_cluster_id}= $val; |
|
41
|
|
|
|
|
|
|
} |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
|
|
44
|
8
|
|
|
8
|
1
|
16
|
sub free_cluster_count($self) { |
|
|
8
|
|
|
|
|
13
|
|
|
|
8
|
|
|
|
|
12
|
|
|
45
|
8
|
|
|
|
|
23
|
my ($sum, $inv, $max)= (0, $self->{_invlist}, $self->{max_cluster_id}); |
|
46
|
8
|
|
|
|
|
25
|
for (my $i= 1; $i < $#$inv; $i += 2) { |
|
47
|
2
|
|
|
|
|
6
|
$sum += $inv->[$i+1] - $inv->[$i]; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
8
|
100
|
|
|
|
19
|
$sum += $max - ($inv->[-1]-1) if defined $max; |
|
50
|
8
|
|
|
|
|
43
|
return $sum; |
|
51
|
|
|
|
|
|
|
} |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
|
|
54
|
3
|
|
|
3
|
1
|
6
|
sub get_chain($self, $cl_id) { |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
4
|
|
|
55
|
3
|
|
|
|
|
14
|
$self->{chains}{$cl_id}; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
|
|
59
|
250
|
|
|
250
|
1
|
162609
|
sub new($class, @attrs) { |
|
|
250
|
|
|
|
|
268
|
|
|
|
250
|
|
|
|
|
247
|
|
|
|
250
|
|
|
|
|
273
|
|
|
60
|
250
|
50
|
33
|
|
|
560
|
my %attrs= @attrs == 1 && ref $attrs[0] eq 'HASH'? %{$attrs[0]} : @attrs; |
|
|
0
|
|
|
|
|
0
|
|
|
61
|
250
|
|
|
|
|
328
|
my $max_cluster_id= delete $attrs{max_cluster_id}; |
|
62
|
250
|
0
|
0
|
|
|
443
|
croak "Invalid max_cluster_id" |
|
|
|
|
33
|
|
|
|
|
|
63
|
|
|
|
|
|
|
unless !defined $max_cluster_id or isa_int $max_cluster_id && $max_cluster_id >= 2; |
|
64
|
250
|
50
|
|
|
|
391
|
carp "Unrecognized attributes ".join(', ', keys %attrs) |
|
65
|
|
|
|
|
|
|
if keys %attrs; |
|
66
|
|
|
|
|
|
|
|
|
67
|
250
|
|
|
|
|
1180
|
bless { |
|
68
|
|
|
|
|
|
|
max_cluster_id => $max_cluster_id, |
|
69
|
|
|
|
|
|
|
fat => [], |
|
70
|
|
|
|
|
|
|
_invlist => [ 0, 2 ], # mark 0-1 as allocated, to remove empty-list edge cases |
|
71
|
|
|
|
|
|
|
chains => {}, |
|
72
|
|
|
|
|
|
|
}, $class; |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
|
|
76
|
19736
|
|
|
19736
|
1
|
18657
|
sub alloc($self, $count) { |
|
|
19736
|
|
|
|
|
18012
|
|
|
|
19736
|
|
|
|
|
19803
|
|
|
|
19736
|
|
|
|
|
17402
|
|
|
77
|
19736
|
50
|
|
|
|
23717
|
return 0 unless $count; |
|
78
|
19736
|
50
|
33
|
|
|
25904
|
croak "Cluster count must be an unsigned integer" |
|
79
|
|
|
|
|
|
|
unless isa_int $count && $count > 0; |
|
80
|
19736
|
|
|
|
|
24748
|
my $inv= $self->{_invlist}; |
|
81
|
19736
|
50
|
|
|
|
23124
|
my $lim= $self->{max_cluster_id}? $self->{max_cluster_id}+1 : undef; |
|
82
|
|
|
|
|
|
|
# If there are enough free sectors, this basically just removes gaps in the inversion list. |
|
83
|
19736
|
|
|
|
|
25941
|
for (my $i= 1; $i < @$inv; $i+= 2) { |
|
84
|
19737
|
|
|
|
|
19886
|
my ($from, $upto)= @{$inv}[$i,$i+1]; |
|
|
19737
|
|
|
|
|
24157
|
|
|
85
|
19737
|
100
|
|
|
|
22710
|
my $n= defined $upto? ($upto - $from) : undef; |
|
86
|
19737
|
100
|
66
|
|
|
26006
|
if (!defined $n || $n >= $count) { |
|
87
|
19736
|
|
|
|
|
18370
|
my @result; |
|
88
|
19736
|
50
|
|
|
|
20382
|
if (!defined $n) { # allocate from final region up to max sector |
|
|
|
0
|
|
|
|
|
|
|
89
|
19736
|
50
|
33
|
|
|
25332
|
last if defined $lim && $lim - $from < $count; |
|
90
|
19736
|
|
|
|
|
31022
|
@result= (splice(@$inv, 1, $i, $from + $count), $from+$count); |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
elsif ($n == $count) { # result comes from exactly the gaps between other allocation |
|
93
|
0
|
|
|
|
|
0
|
@result= splice(@$inv, 1, $i+1); |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
else { # result comes from partial gap |
|
96
|
0
|
|
|
|
|
0
|
@result= (splice(@$inv, 1, $i-1), $from, $from+$count); |
|
97
|
0
|
|
|
|
|
0
|
$inv->[1]= $from + $count; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
# and build the cluster chain in the FAT |
|
100
|
19736
|
|
|
|
|
19543
|
my $prev= 0; |
|
101
|
19736
|
|
|
|
|
26753
|
for (my $j= 0; $j < @result; $j += 2) { |
|
102
|
19737
|
|
|
|
|
31897
|
for ($result[$j] .. ($result[$j+1]-1)) { |
|
103
|
82896
|
100
|
|
|
|
110052
|
$self->{fat}[$prev]= $_ if $prev; |
|
104
|
82896
|
|
|
|
|
93780
|
$prev= $_; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
} |
|
107
|
19736
|
|
|
|
|
22223
|
$self->{fat}[$prev]= 0x0FFFFFFF; |
|
108
|
19736
|
|
|
|
|
45877
|
$self->{chains}{$result[0]}{invlist}= \@result; |
|
109
|
19736
|
|
|
|
|
47488
|
return $result[0]; |
|
110
|
|
|
|
|
|
|
} |
|
111
|
1
|
|
|
|
|
3
|
$count -= $n; |
|
112
|
|
|
|
|
|
|
} |
|
113
|
0
|
|
|
|
|
0
|
return undef; # not enough available |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
|
117
|
8
|
|
|
8
|
1
|
9
|
sub alloc_range($self, $cluster_id, $count) { |
|
|
8
|
|
|
|
|
9
|
|
|
|
8
|
|
|
|
|
9
|
|
|
|
8
|
|
|
|
|
9
|
|
|
|
8
|
|
|
|
|
8
|
|
|
118
|
8
|
50
|
|
|
|
19
|
return 0 unless $count; |
|
119
|
8
|
50
|
33
|
|
|
21
|
croak "Cluster count must be an unsigned integer" |
|
120
|
|
|
|
|
|
|
unless isa_int $count && $count > 0; |
|
121
|
8
|
50
|
33
|
|
|
33
|
croak "Invalid cluster id '$cluster_id'" |
|
122
|
|
|
|
|
|
|
unless isa_int $cluster_id && $cluster_id >= 2; |
|
123
|
8
|
|
|
|
|
23
|
return $self->_alloc_range($cluster_id, $cluster_id + $count); |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
|
|
127
|
360
|
|
|
360
|
1
|
420
|
sub alloc_contiguous($self, $count, $align=1, $align_ofs=0) { |
|
|
360
|
|
|
|
|
407
|
|
|
|
360
|
|
|
|
|
407
|
|
|
|
360
|
|
|
|
|
422
|
|
|
|
360
|
|
|
|
|
385
|
|
|
|
360
|
|
|
|
|
390
|
|
|
128
|
360
|
50
|
|
|
|
562
|
return 0 unless $count; |
|
129
|
360
|
50
|
33
|
|
|
668
|
croak "Cluster count must be an unsigned integer" |
|
130
|
|
|
|
|
|
|
unless isa_int $count && $count > 0; |
|
131
|
360
|
|
|
|
|
538
|
my $inv= $self->{_invlist}; |
|
132
|
360
|
|
|
|
|
666
|
for (my $i= 1; $i < @$inv; $i+=2) { |
|
133
|
437
|
|
|
|
|
476
|
my ($from, $upto)= @{$inv}[$i, $i+1]; |
|
|
437
|
|
|
|
|
688
|
|
|
134
|
437
|
|
|
|
|
442
|
my $start= $from; |
|
135
|
|
|
|
|
|
|
# Align start addr |
|
136
|
437
|
100
|
|
|
|
651
|
if ($align > 1) { |
|
137
|
281
|
|
|
|
|
352
|
my $remainder= ($start - $align_ofs) & ($align-1); |
|
138
|
281
|
100
|
|
|
|
452
|
$start += $align - $remainder if $remainder; |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
# Is the range large enough? |
|
141
|
437
|
100
|
66
|
|
|
990
|
next if defined $upto && $upto - $start < $count; |
|
142
|
360
|
|
|
|
|
665
|
return $self->_alloc_range($start, $start+$count, $i); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
0
|
|
|
|
|
0
|
return undef; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# add the range ($start, $lim) to an inversion list where idx is pointed at |
|
148
|
|
|
|
|
|
|
# the first range that wasn't entirely before $start |
|
149
|
368
|
|
|
368
|
|
355
|
sub _invlist_alloc($inv, $start, $lim, $idx=undef) { |
|
|
368
|
|
|
|
|
409
|
|
|
|
368
|
|
|
|
|
393
|
|
|
|
368
|
|
|
|
|
390
|
|
|
|
368
|
|
|
|
|
362
|
|
|
|
368
|
|
|
|
|
340
|
|
|
150
|
368
|
100
|
|
|
|
550
|
unless (defined $idx) { |
|
151
|
8
|
|
66
|
|
|
54
|
for ($idx= 0; $idx < $#$inv && $inv->[$idx+1] <= $start; $idx++) {} |
|
152
|
|
|
|
|
|
|
# here, [idx] is less/eq start, and [idx+1] is not (or doesn't exist) |
|
153
|
|
|
|
|
|
|
# If idx is even, it means start fell within an allocated range |
|
154
|
8
|
50
|
|
|
|
36
|
return 0 unless $idx & 1; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
368
|
|
|
|
|
444
|
my $from_edge= $inv->[$idx] == $start; |
|
157
|
|
|
|
|
|
|
# allocating at the end |
|
158
|
368
|
50
|
|
|
|
575
|
if ($idx == $#$inv) { |
|
159
|
|
|
|
|
|
|
# max_cluster_id was checked by caller |
|
160
|
368
|
100
|
|
|
|
778
|
$from_edge? ($inv->[$idx]= $lim) |
|
161
|
|
|
|
|
|
|
: push(@$inv, $start, $lim); |
|
162
|
|
|
|
|
|
|
} |
|
163
|
|
|
|
|
|
|
else { |
|
164
|
|
|
|
|
|
|
# does 'lim' exceed the gap between allocations? |
|
165
|
0
|
0
|
|
|
|
0
|
return 0 if $lim > $inv->[$idx+1]; |
|
166
|
0
|
|
|
|
|
0
|
my $to_edge= $lim == $inv->[$idx+1]; |
|
167
|
0
|
0
|
0
|
|
|
0
|
$from_edge && $to_edge? splice(@$inv, $idx, 2) |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
: $from_edge? ($inv->[$idx]= $lim) |
|
169
|
|
|
|
|
|
|
: $to_edge? ($inv->[$idx+1]= $start) |
|
170
|
|
|
|
|
|
|
: splice(@$inv, $idx+1, 0, $start, $lim); |
|
171
|
|
|
|
|
|
|
} |
|
172
|
368
|
|
|
|
|
714
|
return 1; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
368
|
|
|
368
|
|
416
|
sub _alloc_range($self, $cl_start, $cl_lim, $invlist_idx=undef) { |
|
|
368
|
|
|
|
|
404
|
|
|
|
368
|
|
|
|
|
410
|
|
|
|
368
|
|
|
|
|
367
|
|
|
|
368
|
|
|
|
|
351
|
|
|
|
368
|
|
|
|
|
327
|
|
|
176
|
368
|
50
|
33
|
|
|
588
|
return undef if $self->max_cluster_id && $cl_lim-1 > $self->max_cluster_id; |
|
177
|
368
|
50
|
|
|
|
638
|
return undef unless _invlist_alloc($self->{_invlist}, $cl_start, $cl_lim, $invlist_idx); |
|
178
|
|
|
|
|
|
|
# Build the cluster chain in the FAT |
|
179
|
|
|
|
|
|
|
$self->{fat}[$_]= $_+1 |
|
180
|
368
|
|
|
|
|
771
|
for $cl_start .. $cl_lim-2; |
|
181
|
368
|
|
|
|
|
8387
|
$self->{fat}[$cl_lim-1]= 0x0FFFFFFF; |
|
182
|
|
|
|
|
|
|
# An allocation inversion list of one segment |
|
183
|
368
|
|
|
|
|
1093
|
$self->{chains}{$cl_start}{invlist}= [ $cl_start, $cl_lim ]; |
|
184
|
368
|
|
|
|
|
1154
|
return $cl_start; |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
|
|
188
|
1
|
|
|
1
|
1
|
2
|
sub pack($self, $bits=undef) { |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2
|
|
|
189
|
1
|
|
|
|
|
3
|
my $fat= $self->fat; |
|
190
|
1
|
|
33
|
|
|
3
|
my $max= $self->max_cluster_id // $self->max_used_cluster_id; |
|
191
|
1
|
|
|
|
|
2
|
my $cl_count= $max-1; # excluding clusters 0 and 1 |
|
192
|
1
|
50
|
|
|
|
3
|
croak "Max cluster ID exceeds FAT32 max" if $cl_count > FAT32_MAX_CLUSTERS; |
|
193
|
1
|
50
|
|
|
|
3
|
carp "Truncating table to cluster id $max" if $max < $#$fat; |
|
194
|
1
|
|
|
|
|
4
|
$#$fat= $max; |
|
195
|
1
|
|
|
|
|
3
|
$fat->[$_]= 0x0FFFFFFF for 0,1; |
|
196
|
1
|
|
50
|
|
|
39
|
$fat->[$_] //= 0 for 2..$max; # prevent warnings in pack |
|
197
|
1
|
50
|
|
|
|
4
|
if ($cl_count > FAT16_MAX_CLUSTERS) { |
|
|
|
50
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
0
|
return pack 'V*', @$fat; |
|
199
|
|
|
|
|
|
|
} elsif ($cl_count > FAT12_MAX_CLUSTERS) { |
|
200
|
0
|
|
|
|
|
0
|
return pack 'v*', @$fat; |
|
201
|
|
|
|
|
|
|
} else { |
|
202
|
|
|
|
|
|
|
# 12 bits per entry, pack in groups of 3 bytes, little-endian |
|
203
|
1
|
|
|
|
|
2
|
my $buf= "\xFF\xFF\xFF"; |
|
204
|
1
|
|
|
|
|
3
|
for (my $i= 2; $i+1 <= $max; $i+= 2) { |
|
205
|
19
|
|
|
|
|
24
|
my $v= ($fat->[$i] & 0xFFF) | ( ($fat->[$i+1] & 0xFFF) << 12 ); |
|
206
|
19
|
|
|
|
|
34
|
$buf .= pack 'vC', $v, ($v >> 16); |
|
207
|
|
|
|
|
|
|
} |
|
208
|
1
|
50
|
|
|
|
4
|
$buf .= pack 'v', $fat->[$max] & 0xFFF unless $max & 1; |
|
209
|
1
|
|
|
|
|
58
|
return $buf; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Avoiding dependency on namespace::clean |
|
214
|
|
|
|
|
|
|
delete @{Sys::Export::VFAT::AllocationTable::}{qw( carp confess croak refaddr isa_int )}; |
|
215
|
|
|
|
|
|
|
1; |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
__END__ |