diff options
author | Chmouel Boudjnah <chmouel@mandriva.org> | 1999-07-01 12:29:54 +0000 |
---|---|---|
committer | Chmouel Boudjnah <chmouel@mandriva.org> | 1999-07-01 12:29:54 +0000 |
commit | e1729dfdb9c341fe0b9fed7d7b0a80691a547d82 (patch) | |
tree | b72fd8f59af166fe944ebcf114d648ed5644f752 /perl-install/resize_fat | |
parent | b50e655e352e2524fb3fb84b2bb4bc96e6a04cf0 (diff) | |
download | drakx-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/Makefile | 12 | ||||
-rw-r--r-- | perl-install/resize_fat/README | 6 | ||||
-rw-r--r-- | perl-install/resize_fat/any.pm | 82 | ||||
-rw-r--r-- | perl-install/resize_fat/boot_sector.pm | 106 | ||||
-rw-r--r-- | perl-install/resize_fat/dir_entry.pm | 72 | ||||
-rw-r--r-- | perl-install/resize_fat/directory.pm | 78 | ||||
-rw-r--r-- | perl-install/resize_fat/fat.pm | 167 | ||||
-rw-r--r-- | perl-install/resize_fat/info_sector.pm | 36 | ||||
-rw-r--r-- | perl-install/resize_fat/io.pm | 74 | ||||
-rw-r--r-- | perl-install/resize_fat/main.pm | 152 |
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"); +} + |