summaryrefslogtreecommitdiffstats
path: root/perl-install/resize_fat/fat.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl-install/resize_fat/fat.pm')
-rw-r--r--perl-install/resize_fat/fat.pm167
1 files changed, 167 insertions, 0 deletions
diff --git a/perl-install/resize_fat/fat.pm b/perl-install/resize_fat/fat.pm
new file mode 100644
index 000000000..2b64bd6f7
--- /dev/null
+++ b/perl-install/resize_fat/fat.pm
@@ -0,0 +1,167 @@
+package resize_fat::fat;
+
+use diagnostics;
+use strict;
+
+use resize_fat::any;
+use resize_fat::io;
+
+1;
+
+sub read($) {
+ my ($fs) = @_;
+
+ @{$fs->{fats}} = map {
+ my $fat = eval { resize_fat::io::read($fs, $fs->{fat_offset} + $_ * $fs->{fat_size}, $fs->{fat_size}) };
+ $@ and die "reading fat #$_ failed";
+ vec($fat, 0, 8) == $fs->{media} or die "FAT $_ has invalid signature";
+ $fat;
+ } (0 .. $fs->{nb_fats} - 1);
+
+ $fs->{fat} = $fs->{fats}->[0];
+
+ my ($free, $bad, $used) = (0, 0, 0);
+
+ for (my $i = 2; $i < $fs->{nb_clusters} + 2; $i++) {
+ my $cluster = &next($fs, $i);
+ if ($cluster == 0) { $free++; }
+ elsif ($cluster == $resize_fat::bad_cluster_value) { $bad++; }
+ else { $used++; }
+ }
+ @{$fs->{clusters}->{count}}{qw(free bad used)} = ($free, $bad, $used);
+}
+
+sub write($) {
+ my ($fs) = @_;
+
+ sysseek $fs->{fd}, $fs->{fat_offset}, 0 or die "write_fat: seek failed";
+ foreach (1..$fs->{nb_fats}) {
+ syswrite $fs->{fd}, $fs->{fat}, $fs->{fat_size} or die "write_fat: write failed";
+ }
+}
+
+
+
+# allocates where all the clusters will be moved to. Clusters before cut_point
+# remain in the same position, however cluster that are part of a directory are
+# moved regardless (this is a mechanism to prevent data loss) (cut_point is the
+# first cluster that won't occur in the new fs)
+sub allocate_remap {
+ my ($fs, $cut_point) = @_;
+ my ($cluster, $new_cluster);
+ my $remap = sub { $fs->{fat_remap}->[$cluster] = $new_cluster; };
+ my $get_new = sub {
+ $new_cluster = get_free($fs);
+ 0 < $new_cluster && $new_cluster < $cut_point or die "no free clusters";
+ set_eof($fs, $new_cluster); # mark as used
+ #log::ld("resize_fat: [$cluster,", &next($fs, $cluster), "...]->$new_cluster...");
+ };
+
+ $fs->{fat_remap}->[0] = 0;
+ $fs->{last_free_cluster} = 2;
+ for ($cluster = 2; $cluster < $fs->{nb_clusters} + 2; $cluster++) {
+ if ($cluster < $cut_point) {
+ if ($fs->{fat_flag_map}->[$cluster] == $resize_fat::any::DIRECTORY) {
+ &$get_new();
+ } else {
+ $new_cluster = $cluster;
+ }
+ &$remap();
+ } elsif (!is_empty(&next($fs, $cluster))) {
+ &$get_new();
+ &$remap();
+ }
+ }
+}
+
+
+# updates the fat for the resized filesystem
+sub update {
+ my ($fs) = @_;
+
+ for (my $cluster = 2; $cluster < $fs->{nb_clusters} + 2; $cluster++) {
+ if ($fs->{fat_flag_map}->[$cluster]) {
+ my $old_next = &next($fs, $cluster);
+ my $new = $fs->{fat_remap}->[$cluster];
+ my $new_next = $fs->{fat_remap}->[$old_next];
+
+ set_available($fs, $cluster);
+
+ is_eof($old_next) ?
+ set_eof($fs, $new) :
+ set_next($fs, $new, $new_next);
+ }
+ }
+}
+
+
+# - compares the two FATs (one's a backup that should match) - skips first entry
+# - its just a signature (already checked above) NOTE: checks for cross-linking
+# are done in count.c
+sub check($) {
+ my ($fs) = @_;
+ foreach (@{$fs->{fats}}) {
+ $_ eq $fs->{fats}->[0] or die "FAT tables do not match";
+ }
+}
+
+sub endianness16($) { (($_[0] & 0xff) << 8) + ($_[0] >> 8); }
+sub endianness($$) {
+ my ($val, $nb_bits) = @_;
+ my $r = 0;
+ for (; $nb_bits > 0; $nb_bits -= 8) {
+ $r <<= 8;
+ $r += $val & 0xff;
+ $val >>= 8;
+ }
+ $nb_bits < 0 and die "error: endianness only handle numbers divisible by 8";
+ $r;
+}
+
+sub next($$) {
+ my ($fs, $cluster) = @_;
+ $cluster > $fs->{nb_clusters} + 2 and die "fat::next: cluster $cluster outside filesystem";
+ endianness(vec($fs->{fat}, $cluster, $fs->{fs_type_size}), $fs->{fs_type_size});
+
+}
+sub set_next($$$) {
+ my ($fs, $cluster, $new_v) = @_;
+ $cluster > $fs->{nb_clusters} + 2 and die "fat::set_next: cluster $cluster outside filesystem";
+ vec($fs->{fat}, $cluster, $fs->{fs_type_size}) = endianness($new_v, $fs->{fs_type_size});
+}
+
+
+sub get_free($) {
+ my ($fs) = @_;
+ foreach (my $i = 0; $i < $fs->{nb_clusters}; $i++) {
+ my $cluster = ($i + $fs->{last_free_cluster} - 2) % $fs->{nb_clusters} + 2;
+ is_available(&next($fs, $cluster)) and return $fs->{last_free_cluster} = $cluster;
+ }
+ die "no free clusters";
+}
+
+# returns true if <cluster> represents an EOF marker
+sub is_eof($) {
+ my ($cluster) = @_;
+ $cluster >= $resize_fat::bad_cluster_value;
+}
+sub set_eof($$) {
+ my ($fs, $cluster) = @_;
+ set_next($fs, $cluster, $resize_fat::bad_cluster_value + 1);
+}
+
+# returns true if <cluster> is empty. Note that this includes bad clusters.
+sub is_empty($) {
+ my ($cluster) = @_;
+ $cluster == 0 || $cluster == $resize_fat::bad_cluster_value;
+}
+
+# returns true if <cluster> is available.
+sub is_available($) {
+ my ($cluster) = @_;
+ $cluster == 0;
+}
+sub set_available($$) {
+ my ($fs, $cluster) = @_;
+ set_next($fs, $cluster, 0);
+}