summaryrefslogtreecommitdiffstats
path: root/perl-install/resize_fat
diff options
context:
space:
mode:
authorChmouel Boudjnah <chmouel@mandriva.org>1999-07-01 12:29:54 +0000
committerChmouel Boudjnah <chmouel@mandriva.org>1999-07-01 12:29:54 +0000
commite1729dfdb9c341fe0b9fed7d7b0a80691a547d82 (patch)
treeb72fd8f59af166fe944ebcf114d648ed5644f752 /perl-install/resize_fat
parentb50e655e352e2524fb3fb84b2bb4bc96e6a04cf0 (diff)
downloaddrakx-e1729dfdb9c341fe0b9fed7d7b0a80691a547d82.tar
drakx-e1729dfdb9c341fe0b9fed7d7b0a80691a547d82.tar.gz
drakx-e1729dfdb9c341fe0b9fed7d7b0a80691a547d82.tar.bz2
drakx-e1729dfdb9c341fe0b9fed7d7b0a80691a547d82.tar.xz
drakx-e1729dfdb9c341fe0b9fed7d7b0a80691a547d82.zip
"See_The_Changelog"
Diffstat (limited to 'perl-install/resize_fat')
-rw-r--r--perl-install/resize_fat/Makefile12
-rw-r--r--perl-install/resize_fat/README6
-rw-r--r--perl-install/resize_fat/any.pm82
-rw-r--r--perl-install/resize_fat/boot_sector.pm106
-rw-r--r--perl-install/resize_fat/dir_entry.pm72
-rw-r--r--perl-install/resize_fat/directory.pm78
-rw-r--r--perl-install/resize_fat/fat.pm167
-rw-r--r--perl-install/resize_fat/info_sector.pm36
-rw-r--r--perl-install/resize_fat/io.pm74
-rw-r--r--perl-install/resize_fat/main.pm152
10 files changed, 785 insertions, 0 deletions
diff --git a/perl-install/resize_fat/Makefile b/perl-install/resize_fat/Makefile
new file mode 100644
index 000000000..34c257a4e
--- /dev/null
+++ b/perl-install/resize_fat/Makefile
@@ -0,0 +1,12 @@
+PRODUCT = libresize
+TARSOURCE = $(PRODUCT).tar.bz2
+
+.PHONY: clean tar
+
+clean:
+ rm -f *~ TAGS $(TARSOURCE)
+
+tar: clean
+ cp -f ../common.pm .
+ cd .. ; tar cfy $(TARSOURCE) $(PRODUCT) ; mv $(TARSOURCE) $(PRODUCT)
+ rm -f common.pm
diff --git a/perl-install/resize_fat/README b/perl-install/resize_fat/README
new file mode 100644
index 000000000..2910c06c3
--- /dev/null
+++ b/perl-install/resize_fat/README
@@ -0,0 +1,6 @@
+just do ./resize.pm and look at usage.
+
+
+BUGS:
+no known bugs :)
+if you found one, please mail pixel@linux-mandrake.com !!
diff --git a/perl-install/resize_fat/any.pm b/perl-install/resize_fat/any.pm
new file mode 100644
index 000000000..d78a342be
--- /dev/null
+++ b/perl-install/resize_fat/any.pm
@@ -0,0 +1,82 @@
+package resize_fat::any;
+
+use diagnostics;
+use strict;
+use vars qw($FREE $FILE $DIRECTORY);
+
+use common qw(:common :constant);
+use resize_fat::fat;
+use resize_fat::directory;
+use resize_fat::dir_entry;
+
+
+$FREE = 0;
+$FILE = 1;
+$DIRECTORY = 2;
+
+
+1;
+
+
+# returns the number of clusters for a given filesystem type
+sub min_cluster_count($) {
+ my ($fs) = @_;
+ (1 << $ {{ FAT16 => 12, FAT32 => 16 }}{$fs->{fs_type}}) - 12;
+}
+sub max_cluster_count($) {
+ my ($fs) = @_;
+ $resize_fat::bad_cluster_value - 2;
+}
+
+
+
+# calculates the minimum size of a partition, in physical sectors
+sub min_size($) {
+ my ($fs) = @_;
+ my $count = $fs->{clusters}->{count};
+
+ # directories are both in `used' and `dirs', so are counted twice
+ # It's done on purpose since we're moving all directories. So at the worse
+ # moment, 2 directories are there, but that way nothing wrong can happen :)
+ my $min_cluster_count = max(2 + $count->{used} + $count->{bad} + $count->{dirs}, min_cluster_count($fs));
+
+ $min_cluster_count * divide($fs->{cluster_size}, $SECTORSIZE) +
+ divide($fs->{cluster_offset}, $SECTORSIZE);
+}
+# calculates the maximum size of a partition, in physical sectors
+sub max_size($) {
+ my ($fs) = @_;
+
+ my $max_cluster_count = min($fs->{nb_fat_entries} - 2, max_cluster_count($fs));
+
+ $max_cluster_count * divide($fs->{cluster_size}, $SECTORSIZE) +
+ divide($fs->{cluster_offset}, $SECTORSIZE);
+}
+
+# fills in $fs->{fat_flag_map}.
+# Each FAT entry is flagged as either FREE, FILE or DIRECTORY.
+sub flag_clusters {
+ my ($fs) = @_;
+ my ($cluster, $entry, $type);
+
+ my $f = sub {
+ ($entry) = @_;
+ $cluster = resize_fat::dir_entry::get_cluster($entry);
+
+ if (resize_fat::dir_entry::is_file($entry)) {
+ $type = $FILE;
+ } elsif (resize_fat::dir_entry::is_directory($entry)) {
+ $type = $DIRECTORY;
+ } else { return }
+
+ for (; !resize_fat::fat::is_eof($cluster); $cluster = resize_fat::fat::next($fs, $cluster)) {
+ $cluster == 0 and die "Bad FAT: unterminated chain for $entry->{name}\n";
+ $fs->{fat_flag_map}->[$cluster] and die "Bad FAT: cluster $cluster is cross-linked for $entry->{name}\n";
+ $fs->{fat_flag_map}->[$cluster] = $type;
+ $fs->{clusters}->{count}->{dirs}++ if $type == $DIRECTORY;
+ }
+ };
+ $fs->{fat_flag_map} = [ ($FREE) x ($fs->{nb_clusters} + 2) ];
+ $fs->{clusters}->{count}->{dirs} = 0;
+ resize_fat::directory::traverse_all($fs, $f);
+}
diff --git a/perl-install/resize_fat/boot_sector.pm b/perl-install/resize_fat/boot_sector.pm
new file mode 100644
index 000000000..c236b1617
--- /dev/null
+++ b/perl-install/resize_fat/boot_sector.pm
@@ -0,0 +1,106 @@
+package resize_fat::boot_sector;
+
+use diagnostics;
+use strict;
+
+use common qw(:common :system :constant);
+use resize_fat::io;
+use resize_fat::any;
+use resize_fat::directory;
+
+
+my $format = "a3 a8 S C S C S S C S S S I I I S S I S S a458 S";
+my @fields = (
+ 'boot_jump', # boot strap short or near jump
+ 'system_id', # Name - can be used to special case partition manager volumes
+ 'sector_size', # bytes per logical sector
+ 'cluster_size_in_sectors', # sectors/cluster
+ 'nb_reserved', # reserved sectors
+ 'nb_fats', # number of FATs
+ 'nb_root_dir_entries', # number of root directory entries
+ 'small_nb_sectors', # number of sectors: big_nb_sectors supersedes
+ 'media', # media code
+ 'fat16_fat_length', # sectors/FAT for FAT12/16
+ 'sectors_per_track',
+ 'nb_heads',
+ 'nb_hidden', # (unused)
+ 'big_nb_sectors', # number of sectors (if small_nb_sectors == 0)
+
+# FAT32-only entries
+ 'fat32_fat_length', # size of FAT in sectors
+ 'fat32_flags', # bit8: fat mirroring,
+ # low4: active fat
+ 'fat32_version', # minor * 256 + major
+ 'fat32_root_dir_cluster',
+ 'info_offset_in_sectors',
+ 'fat32_backup_sector',
+
+# Common again...
+ 'boot_code', # Boot code (or message)
+ 'boot_sign', # 0xAA55
+);
+
+1;
+
+
+# trimfs_init_boot_sector() - reads in the boot sector - gets important info out
+# of boot sector, and puts in main structure - performs sanity checks - returns 1
+# on success, 0 on failureparameters: filesystem an empty structure to fill.
+sub read($) {
+ my ($fs) = @_;
+
+ my $boot = eval { resize_fat::io::read($fs, 0, $SECTORSIZE) }; $@ and die "reading boot sector failed on device $fs->{fs_name}";
+ @{$fs}{@fields} = unpack $format, $boot;
+
+ $fs->{nb_sectors} = $fs->{small_nb_sectors} || $fs->{big_nb_sectors};
+ $fs->{cluster_size} = $fs->{cluster_size_in_sectors} * $fs->{sector_size};
+
+ $fs->{boot_sign} == 0xAA55 or die "Invalid signature for a MS-based filesystem.";
+ $fs->{nb_fats} == 2 or die "Weird number of FATs: $fs->{nb_fats}, not 2.",
+ $fs->{nb_sectors} < 32 and die "Too few sectors for viable file system\n";
+
+ if ($fs->{fat16_fat_length}) {
+ # asserting FAT16, will be verified later on
+ $fs->{fs_type} = 'FAT16';
+ $fs->{fs_type_size} = 16;
+ $fs->{fat_length} = $fs->{fat16_fat_length};
+ } else {
+ $resize_fat::isFAT32 = 1;
+ $fs->{fs_type} = 'FAT32';
+ $fs->{fs_type_size} = 32;
+ $fs->{fat_length} = $fs->{fat32_fat_length};
+
+ $fs->{nb_root_dir_entries} = 0;
+ $fs->{info_offset} = $fs->{info_offset_in_sectors} * $fs->{sector_size};
+ }
+ $resize_fat::bad_cluster_value = (1 << $fs->{fs_type_size}) - 9;
+
+ $fs->{fat_offset} = $fs->{nb_reserved} * $fs->{sector_size};
+ $fs->{fat_size} = $fs->{fat_length} * $fs->{sector_size};
+ $fs->{root_dir_offset} = $fs->{fat_offset} + $fs->{fat_size} * $fs->{nb_fats};
+ $fs->{root_dir_size} = $fs->{nb_root_dir_entries} * resize_fat::directory::entry_size();
+ $fs->{cluster_offset} = $fs->{root_dir_offset} + $fs->{root_dir_size} - 2 * $fs->{cluster_size};
+
+ $fs->{nb_fat_entries} = divide($fs->{fat_size}, $fs->{fs_type_size} / 8);
+
+ # - 2 because clusters 0 & 1 doesn't exist
+ $fs->{nb_clusters} = divide($fs->{nb_sectors} * $fs->{sector_size} - $fs->{cluster_offset}, $fs->{cluster_size}) - 2;
+
+ $fs->{dir_entries_per_cluster} = divide($fs->{cluster_size}, psizeof($format));
+
+ $fs->{nb_clusters} >= resize_fat::any::min_cluster_count($fs) or die "error: not enough sectors for a $fs->{fs_type}\n";
+ $fs->{nb_clusters} < resize_fat::any::max_cluster_count($fs) or die "error: too many sectors for a $fs->{fs_type}\n";
+}
+
+sub write($) {
+ my ($fs) = @_;
+ my $boot = pack($format, @{$fs}{@fields});
+
+ eval { resize_fat::io::write($fs, 0, $SECTORSIZE, $boot) }; $@ and die "writing the boot sector failed on device $fs->{fs_name}";
+
+ if ($resize_fat::isFAT32) {
+ # write backup
+ eval { resize_fat::io::write($fs, $fs->{fat32_backup_sector} * $SECTORSIZE, $SECTORSIZE, $boot) };
+ $@ and die "writing the backup boot sector (#$fs->{fat32_backup_sector}) failed on device $fs->{fs_name}";
+ }
+}
diff --git a/perl-install/resize_fat/dir_entry.pm b/perl-install/resize_fat/dir_entry.pm
new file mode 100644
index 000000000..fa5ebb344
--- /dev/null
+++ b/perl-install/resize_fat/dir_entry.pm
@@ -0,0 +1,72 @@
+package resize_fat::dir_entry;
+
+use diagnostics;
+use strict;
+
+
+my $DELETED_FLAG = 0xe5;
+my $VOLUME_LABEL_ATTR = 0x08;
+my $VFAT_ATTR = 0x0f;
+my $DIRECTORY_ATTR = 0x10;
+
+1;
+
+sub get_cluster($) {
+ my ($entry) = @_;
+ $entry->{first_cluster} + ($resize_fat::isFAT32 ? $entry->{first_cluster_high} * 65536 : 0);
+}
+sub set_cluster($$) {
+ my ($entry, $val) = @_;
+ $entry->{first_cluster} = $val & (1 << 16) - 1;
+ $entry->{first_cluster_high} = $val >> 16 if $resize_fat::isFAT32;
+}
+
+sub is_directory_raw($) {
+ my ($entry) = @_;
+ !is_special_entry($entry) && $entry->{attributes} & $DIRECTORY_ATTR;
+}
+
+sub is_directory($) {
+ my ($entry) = @_;
+ is_directory_raw($entry) && $entry->{name} !~ /^\.\.? /;
+}
+
+sub is_volume($) {
+ my ($entry) = @_;
+ !is_special_entry($entry) && $entry->{attributes} & $VOLUME_LABEL_ATTR;
+}
+
+sub is_file($) {
+ my ($entry) = @_;
+ !is_special_entry($entry) && !is_directory($entry) && !is_volume($entry) && $entry->{length};
+}
+
+
+sub is_special_entry($) {
+ my ($entry) = @_;
+ my ($c) = unpack "C", $entry->{name};
+
+ # skip empty slots, deleted files, and 0xF6?? (taken from kernel)
+ $c == 0 || $c == $DELETED_FLAG || $c == 0xF6 and return 1;
+
+ $entry->{attributes} == $VFAT_ATTR and return 1;
+ 0;
+}
+
+
+# return true if entry has been modified
+sub remap {
+ my ($fat_remap, $entry) = @_;
+
+ is_special_entry($entry) and return;
+
+ my $cluster = get_cluster($entry);
+ my $new_cluster = $fat_remap->[$cluster];
+
+ #print "remapping cluster ", get_first_cluster($fs, $entry), " to $new_cluster";
+
+ $new_cluster == $cluster and return; # no need to modify
+
+ set_cluster($entry, $new_cluster);
+ 1;
+}
diff --git a/perl-install/resize_fat/directory.pm b/perl-install/resize_fat/directory.pm
new file mode 100644
index 000000000..ab8ec5328
--- /dev/null
+++ b/perl-install/resize_fat/directory.pm
@@ -0,0 +1,78 @@
+package resize_fat::directory;
+
+use diagnostics;
+use strict;
+
+use common qw(:system);
+use resize_fat::dir_entry;
+use resize_fat::io;
+
+
+my $format = "a8 a3 C C C S7 I";
+my @fields = (
+ 'name',
+ 'extension',
+ 'attributes',
+ 'is_upper_case_name',
+ 'creation_time_low', # milliseconds
+ 'creation_time_high',
+ 'creation_date',
+ 'access_date',
+ 'first_cluster_high', # for FAT32
+ 'time',
+ 'date',
+ 'first_cluster',
+ 'length',
+);
+
+1;
+
+sub entry_size { psizeof($format) }
+
+# call `f' for each entry of the directory
+# if f return true, then modification in the entry are taken back
+sub traverse($$$) {
+ my ($fs, $directory, $f) = @_;
+
+ for (my $i = 0; 1; $i++) {
+ my $raw = \substr($directory, $i * psizeof($format), psizeof($format));
+
+ # empty entry means end of directory
+ $$raw =~ /^\0*$/ and return $directory;
+
+ my $entry; @{$entry}{@fields} = unpack $format, $$raw;
+
+ &$f($entry)
+ and $$raw = pack $format, @{$entry}{@fields};
+ }
+ $directory;
+}
+
+sub traverse_all($$) {
+ my ($fs, $f) = @_;
+
+ my $traverse_all; $traverse_all = sub {
+ my ($entry) = @_;
+
+ &$f($entry);
+
+ resize_fat::dir_entry::is_directory($entry)
+ and traverse($fs, resize_fat::io::read_file($fs, resize_fat::dir_entry::get_cluster($entry)), $traverse_all);
+
+ undef; # no need to write back (cf traverse)
+ };
+
+ my $directory = $resize_fat::isFAT32 ?
+ resize_fat::io::read_file($fs, $fs->{fat32_root_dir_cluster}) :
+ resize_fat::io::read($fs, $fs->{root_dir_offset}, $fs->{root_dir_size});
+ traverse($fs, $directory, $traverse_all);
+}
+
+
+# function used by construct_dir_tree to translate the `cluster' fields in each
+# directory entry
+sub remap {
+ my ($fs, $directory) = @_;
+
+ traverse($fs->{fat_remap}, $directory, sub { resize_fat::dir_entry::remap($fs->{fat_remap}, $_[0]) });
+}
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);
+}
diff --git a/perl-install/resize_fat/info_sector.pm b/perl-install/resize_fat/info_sector.pm
new file mode 100644
index 000000000..c46ae15fc
--- /dev/null
+++ b/perl-install/resize_fat/info_sector.pm
@@ -0,0 +1,36 @@
+package resize_fat::info_sector;
+
+use diagnostics;
+use strict;
+
+use common qw(:system);
+use resize_fat::io;
+
+my $format = "a484 I I I a16";
+my @fields = (
+ 'unused',
+ 'signature', # should be 0x61417272
+ 'free_clusters', # -1 for unknown
+ 'next_cluster', # most recently allocated cluster
+ 'unused2',
+);
+
+1;
+
+
+sub read($) {
+ my ($fs) = @_;
+ my $info = resize_fat::io::read($fs, $fs->{offset}, psizeof($format));
+ @{$fs->{info_sector}}{@fields} = unpack $format, $info;
+ $fs->{info_sector}->{signature} == 0x61417272 or die "Invalid information sector signature\n";
+}
+
+sub write($) {
+ my ($fs) = @_;
+ $fs->{info_sector}->{free_clusters} = $fs->{clusters}->{count}->{free};
+ $fs->{info_sector}->{next_cluster} = 2;
+
+ my $info = pack $format, @{$fs->{info_sector}}{@fields};
+
+ resize_fat::io::write($fs, $fs->{info_offset}, psizeof($format), $info);
+}
diff --git a/perl-install/resize_fat/io.pm b/perl-install/resize_fat/io.pm
new file mode 100644
index 000000000..8ffaa8355
--- /dev/null
+++ b/perl-install/resize_fat/io.pm
@@ -0,0 +1,74 @@
+package resize_fat::io;
+
+use diagnostics;
+use strict;
+
+use resize_fat::fat;
+
+1;
+
+
+sub read($$$) {
+ my ($fs, $pos, $size) = @_;
+ my $buf;
+ sysseek $fs->{fd}, $pos, 0 or die "seeking to byte #$pos failed on device $fs->{fs_name}";
+ sysread $fs->{fd}, $buf, $size or die "reading at byte #$pos failed on device $fs->{fs_name}";
+ $buf;
+}
+sub write($$$$) {
+ my ($fs, $pos, $size, $buf) = @_;
+ sysseek $fs->{fd}, $pos, 0 or die "seeking to byte #$pos failed on device $fs->{fs_name}";
+ syswrite $fs->{fd}, $buf, $size or die "writing at byte #$pos failed on device $fs->{fs_name}";
+}
+
+sub read_cluster($$) {
+ my ($fs, $cluster) = @_;
+ my $buf;
+
+ eval {
+ $buf = &read($fs,
+ $fs->{cluster_offset} + $cluster * $fs->{cluster_size},
+ $fs->{cluster_size});
+ }; @$ and die "reading cluster #$cluster failed on device $fs->{fs_name}";
+ $buf;
+}
+sub write_cluster($$$) {
+ my ($fs, $cluster, $buf) = @_;
+
+ eval {
+ &write($fs,
+ $fs->{cluster_offset} + $cluster * $fs->{cluster_size},
+ $fs->{cluster_size},
+ $buf);
+ }; @$ and die "writing cluster #$cluster failed on device $fs->{fs_name}";
+}
+
+sub read_file($$) {
+ my ($fs, $cluster) = @_;
+ my $buf = '';
+
+ for (; !resize_fat::fat::is_eof($cluster); $cluster = resize_fat::fat::next($fs, $cluster)) {
+ $cluster == 0 and die "Bad FAT: unterminated chain\n";
+ $buf .= read_cluster($fs, $cluster);
+ }
+ $buf;
+}
+
+sub check_mounted($) {
+ my ($f) = @_;
+
+ local *F;
+ open F, "/proc/mounts" or die "error opening /proc/mounts\n";
+ foreach (<F>) {
+ /^$f\s/ and die "device is mounted";
+ }
+}
+
+sub open($) {
+ my ($fs) = @_;
+
+ check_mounted($fs->{device});
+
+ sysopen F, $fs->{fs_name}, 2 or sysopen F, $fs->{fs_name}, 0 or die "error opening device $fs->{fs_name} for writing\n";
+ $fs->{fd} = \*F;
+}
diff --git a/perl-install/resize_fat/main.pm b/perl-install/resize_fat/main.pm
new file mode 100644
index 000000000..2d5f4f969
--- /dev/null
+++ b/perl-install/resize_fat/main.pm
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+
+package resize_fat::main;
+
+use diagnostics;
+use strict;
+
+use log;
+use common qw(:common :system :constant);
+use resize_fat::boot_sector;
+use resize_fat::info_sector;
+use resize_fat::directory;
+use resize_fat::io;
+use resize_fat::fat;
+use resize_fat::any;
+
+
+#@ARGV == 2 or die "usage: fatresize <device> <size>\n <size> = 100 means `resize to 100Mb'\n <size> = +10 means `keep 10Mb of free space'\n";
+#
+#my $fs = init($ARGV[0]);
+#resize($fs, $ARGV[1]);
+
+1;
+
+# - reads in the boot sector/partition info., and tries to make some sense of it
+sub new($$$) {
+ my ($type, $device, $fs_name) = @_;
+ my $fs = { device => $device, fs_name => $fs_name } ;
+
+ resize_fat::io::open($fs);
+ resize_fat::boot_sector::read($fs);
+ $resize_fat::isFAT32 and eval { resize_fat::info_sector::read($fs) };
+ resize_fat::fat::read($fs);
+ resize_fat::fat::check($fs);
+ resize_fat::any::flag_clusters($fs);
+
+ bless $fs, $type;
+}
+
+# copy all clusters >= <start_cluster> to a new place on the partition, less
+# than <start_cluster>. Only copies files, not directories.
+# (use of buffer needed because the seeks slow like hell the hard drive)
+sub copy_clusters {
+ my ($fs, $cluster) = @_;
+ my @buffer;
+ my $flush = sub {
+ while (@buffer) {
+ my $cluster = shift @buffer;
+ resize_fat::io::write_cluster($fs, $cluster, shift @buffer);
+ }
+ };
+ for (; $cluster < $fs->{nb_clusters} + 2; $cluster++) {
+ $fs->{fat_flag_map}->[$cluster] == $resize_fat::any::FILE or next;
+ push @buffer, $fs->{fat_remap}->[$cluster], resize_fat::io::read_cluster($fs, $cluster);
+ @buffer > 50 and &$flush();
+ }
+ &$flush();
+}
+
+# Constructs the new directory tree to match the new file locations.
+sub construct_dir_tree {
+ my ($fs) = @_;
+
+ if ($resize_fat::isFAT32) {
+ # fat32's root must remain in the first 64k clusters
+ # so don't set it as DIRECTORY, it will be specially handled
+ $fs->{fat_flag_map}->[$fs->{fat32_root_dir_cluster}] = $resize_fat::any::FREE;
+ }
+
+ for (my $cluster = 2; $cluster < $fs->{nb_clusters} + 2; $cluster++) {
+ $fs->{fat_flag_map}->[$cluster] == $resize_fat::any::DIRECTORY or next;
+
+ resize_fat::io::write_cluster($fs,
+ $fs->{fat_remap}->[$cluster],
+ resize_fat::directory::remap($fs, resize_fat::io::read_cluster($fs, $cluster)));
+ }
+
+ sync();
+
+ # until now, only free clusters have been written. it's a null operation if we stop here.
+ # it means no corruption :)
+ #
+ # now we must be as fast as possible!
+
+ # remapping non movable root directory
+ if ($resize_fat::isFAT32) {
+ my $cluster = $fs->{fat32_root_dir_cluster};
+
+ resize_fat::io::write_cluster($fs,
+ $fs->{fat_remap}->[$cluster],
+ resize_fat::directory::remap($fs, resize_fat::io::read_cluster($fs, $cluster)));
+ } else {
+ resize_fat::io::write($fs, $fs->{root_dir_offset}, $fs->{root_dir_size},
+ resize_fat::directory::remap($fs, resize_fat::io::read($fs, $fs->{root_dir_offset}, $fs->{root_dir_size})));
+ }
+}
+
+sub min_size($) { &resize_fat::any::min_size }
+sub max_size($) { &resize_fat::any::max_size }
+
+# resize
+# - size is in sectors
+# - checks boundaries before starting
+# - copies all data beyond new_cluster_count behind the frontier
+sub resize {
+ my ($fs, $size) = @_;
+
+ my ($min, $max) = (min_size($fs), max_size($fs));
+
+
+ $size += $min if $size =~ /^\+/;
+
+ $size >= $min or die "Minimum filesystem size is $min sectors";
+ $size <= $max or die "Maximum filesystem size is $max sectors";
+
+ log::l("resize_fat: Partition size fill be ", $size * $SECTORSIZE >> 20, "Mb (well exactly ${size} sectors)");
+
+ my $new_data_size = $size * $SECTORSIZE - $fs->{cluster_offset};
+ my $new_nb_clusters = divide($new_data_size, $fs->{cluster_size});
+
+ log::l("resize_fat: Allocating new clusters");
+ resize_fat::fat::allocate_remap($fs, $new_nb_clusters);
+
+ log::l("resize_fat: Copying files");
+ copy_clusters($fs, $new_nb_clusters);
+
+ log::l("resize_fat: Copying directories");
+ construct_dir_tree($fs);
+
+ log::l("Writing new FAT...");
+ resize_fat::fat::update($fs);
+ resize_fat::fat::write($fs);
+
+ $fs->{nb_sectors} = $size;
+ $fs->{nb_clusters} = $new_nb_clusters;
+ $fs->{clusters}->{count}->{free} =
+ $fs->{nb_clusters} - $fs->{clusters}->{count}->{used} - $fs->{clusters}->{count}->{bad};
+
+ $fs->{system_id} = 'was here!';
+ $fs->{small_nb_sectors} = 0;
+ $fs->{big_nb_sectors} = $size;
+
+ log::l("resize_fat: Writing new boot sector...");
+
+ resize_fat::boot_sector::write($fs);
+
+ $resize_fat::isFAT32 and eval { resize_fat::info_sector::write($fs) }; # doesn't matter if this fails - its pretty useless!
+
+ sync();
+ log::l("resize_fat: done");
+}
+