#!/usr/bin/perl

# DiskDrake
# Copyright (C) 1999 MandrakeSoft (pixel@linux-mandrake.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# This is mainly a perl rewrite of the work of Andrew Clausen (libresize)

package resize_fat::main; # $Id$

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;


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 } ;

    eval {
	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::any::flag_clusters($fs);
    };
    if ($@) {
	close $fs->{fd};
	die;
    }
    bless $fs, $type;
}

sub DESTROY {
    my ($fs) = @_;
    close $fs->{fd};
    resize_fat::c_rewritten::free_all();
}

#- 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++) {
	resize_fat::c_rewritten::flag($cluster) == $resize_fat::any::FILE or next;
	push @buffer, 
	  resize_fat::c_rewritten::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
	resize_fat::c_rewritten::set_flag($fs->{fat32_root_dir_cluster}, $resize_fat::any::FREE);
    }

    for (my $cluster = 2; $cluster < $fs->{nb_clusters} + 2; $cluster++) {
	resize_fat::c_rewritten::flag($cluster) == $resize_fat::any::DIRECTORY or next;

      resize_fat::io::write_cluster($fs,
				    resize_fat::c_rewritten::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,
		      resize_fat::c_rewritten::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 }
sub used_size($) { &resize_fat::any::used_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 will 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});
    my $used_size = used_size($fs);

    log::l("resize_fat: Break point for moving files is ". ($used_size * $SECTORSIZE >> 20) ." Mb ($used_size sectors)");
    if ($size < $used_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);
    } else {
	log::l("resize_fat: Nothing need to be moved");
    }

    $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} - 2;

    $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();
    close $fs->{fd};
    log::l("resize_fat: done");
}