#!/usr/bin/env perl

# CloneHDD is a Perl script, which make clone (backup) your disk partitions (OS FreeBSD only)
# Copyright (C) 2007 Anton Lysenok. bart@tapolsky.net.ua
#
# 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
# Also add information on how to contact you by electronic and paper mail.


### WARNING ### WARNING ### WARNING ### WARNING ### WARNING ### WARNING ###
#    DO NOT MODIFY SOURCE CODE! ALL VARIABLES ARE SET IN COMMAND LINE.    #
#               PLEASE, READ DOCUMENTATION BEFORE USE                     #
### WARNING ### WARNING ### WARNING ### WARNING ### WARNING ### WARNING ###


#Path to external programs
$mount='/sbin/mount';
$df='/bin/df';
$fdisk='/sbin/fdisk';
$dd='/bin/dd';
$echo='/bin/echo';
$bsdlabel='/sbin/bsdlabel';
$newfs='/sbin/newfs';
$mount='/sbin/mount';
$umount='/sbin/umount';
$dump='/sbin/dump';
$restore='/sbin/restore';
$yes='/usr/bin/yes';
$date='/bin/date';

#Defaults
$freesp=100*2048;
$safe=0;
$cyl=63;
$force=0;

#Disable STDOUT buffer
$|=1;

sub usage
    {
    print "Usage:\n";
    print "\t-src=[src partition] *\n";
    print "\t-dst=[dst partition] *\n";
    print "\t-swap=[swap size] *\n";
    print "\t-freespace=[Required free space on dst partition] Default: 100 MB\n";
    print "\t-safe [Required safe mode for `dump`] Default: Disabled\n";
    print "\t-fstab=[Device name to write in fstab] Default: src partition name\n";
    print "\t-force [Do not ask any questions] Default: Disabled\n";
    print "\n\t* - required parameters\n\n";
    exit;
    }

#Partition identificators
@letters=('d','e','f','g','h');

#Order of standart partitions
@stdpartitions=('/tmp','/var','/usr');

foreach(@ARGV)
    {
    if(/^-dst=(\S+)$/){$dst=$1;}
    if(/^-src=(\S+)$/){$src=$1;}
    if(/^-swap=(\d+)$/){$swap=$1;}
    if(/^-freespace=(\d+)$/){$freesp=$1*2048;}
    if(/^-fstab=(\S+)$/){$fstab=$1;}
    if(/^-safe$/){$safe=1;}
    if(/^-force$/){$force=1;}
    }    
if($fstab eq ''){$fstab=$src;}
    
system("clear");

#Check required parameters    
if(($src eq '')||($dst eq '')||($swap eq '')){usage();}

#Print input parameters
print "Clone parameters:\n";
print "Source partition: /dev/$src\n";
print "Dest partition: /dev/$dst\n";
print "Swap size: $swap MB\n";
if($safe){$buff="Enabled";}else{$buff="Disabled";}
print "Safe dumping: $buff\n";
print "Free space on DST: ";print $freesp/2048;print " MB\n";
print "Fstab device name: $fstab\n";
print "---\n";

#Check if script ran from backup device
if(-e "/backup-mode"){print STDERR "You are working from backup device. Please stop clone script usage!!!\n";exit;}

#Check for valid partition names
if(!-e "/dev/$src"){print STDERR "[ERR] Device $src is not exist!\n";exit;}
if(!-e "/dev/$dst"){print STDERR "[ERR] Device $dst is not exist!\n";exit;}
print "[OK] Found devices for clone procedure\n";

#Check if DST partition is not mounted
@mount=`$mount`;
foreach(@mount)
    {
    if(/\/dev\/$dst/){print STDERR "[ERR] Partition $dst is mounted. Check input parameters or you can damage your data!\n";exit;}
    }
print "[OK] DST partitions are not in use\n";
print "---\n";

#Define src partitions size
@df=`$df -b`;
%srcprt=();
%srcused=();
@realpartitions=();
@needpartitions=();
foreach $a (@df)
    {
    chomp($a);
    if($a=~/^\/dev\/$src\S+\s+(\d+)\s+(\d+).+\s+(\/\S*)$/)
	{
	$srcprt{$3}=$1;
	$srcused{$3}=$2;
	if($3 ne '/'){push(@realpartitions,$3);}
	}
    }

foreach $a (@stdpartitions)
    {
    $buff=0;
    foreach $b (@realpartitions)
	{
	if($a eq $b){$buff=1;}
	}
    if($buff){push(@needpartitions,$a);}
    }    
    
foreach $a (@realpartitions)
    {
    $buff=0;
    foreach $b (@stdpartitions)
	{
	if($a eq $b){$buff=1;}
	}
    if(!$buff){push(@needpartitions,$a);}
    }    

$minsize=0;
$srcsize=0;
print "Source partition\n";
foreach $k (keys %srcused)
    {
    $minsize+=$srcused{$k};
    $srcsize+=$srcprt{$k};
    print "$k size: ";
    print sprintf "%0.0d",$srcprt{$k}/2048;
    print "MB, used: ";
    print sprintf "%0.0d",$srcused{$k}/2048;
    print "MB\n";
    }
    
print "Total: ";
print sprintf "%0.0d",$srcsize/2048;
print " MB, used: ";
print sprintf "%0.0d", $minsize/2048;
print " MB\n";
print "---\n";

#Define dst device size
$buf=`$fdisk /dev/$dst 2>/dev/null`;
$buf=~/in-core disklabel are:\ncylinders=(\d+).*\((\d+) blks\/cyl\)/;
$dstsize=$1*$2;
$dstsize-=$cyl;
#print $dstsize;

#Check if we have low space on dst device
if($dstsize-$minsize-$freesp<0){print STDERR "[ERR] There are not enough space on $dst device!\n";exit;}
print "[OK] Device $dst has enough free space\n";
print "DATA ON DEVICE $dst WILL BE DESTROYED NOW!\n";
#Pause
if(!$force){
    print "Continue? [yes/no]: ";
    $buff=<STDIN>;chomp($buff);
    if($buff ne 'yes'){print "You have not type \"yes\". exiting.\n";exit;}
}

print "Wait 10 seconds before start:";
for($i=10;$i>0;$i--){print " $i";sleep 1;}
print "\n";


#Clear dst device
`$dd if=/dev/zero of=/dev/$dst count=3200 2>/dev/null`;
`$echo "p 1 0 0 0" | $fdisk -f - /dev/$dst 2>/dev/null`;      
`$echo "p 2 0 0 0" | $fdisk -f - /dev/$dst 2>/dev/null`;      
`$echo "p 3 0 0 0" | $fdisk -f - /dev/$dst 2>/dev/null`;      
`$echo "p 4 0 0 0" | $fdisk -f - /dev/$dst 2>/dev/null`;      
print "[OK] Device /dev/$dst made clean\n";

#Creation of fs slice, which cover entire dst device
`$echo "p 1 165 $cyl $dstsize" | $fdisk -f - /dev/$dst 2>/dev/null`;      
`$echo "a 1" | $fdisk -f - /dev/$dst 2>/dev/null`;
`$yes | $fdisk -B -b /boot/mbr /dev/$dst 2>/dev/null`;

#check if head boundary is not on 63 block
$buff=`$fdisk -s /dev/$dst`;
$buff=~/\s+1:\s+(\d+)\s+(\d+)\s+/;
$adjust_start=$1;
$adjust_size=$2;
if($cyl!=$adjust_start){print "[WARN] Start block of slice was adjusted from $cyl to $adjust_start\n";}
if($dstsize!=$adjust_size){print "[WARN] Size of slice was adjusted from $dstsize to $adjust_size\n";}
$dstsize=$adjust_size;

#Begin slice formatting
$pname=$dst."s1";
`$dd if=/dev/zero of=/dev/$pname count=3200 2>/dev/null`;

print "[OK] New slice created\n---\n";

#Writing partition table
print "Destination device partitions:\n";
$offset=0;
open(LABEL,">dstlabels");
print LABEL "8 partitions:\n";

#Extended
print LABEL "c: $dstsize 0 unused\n";

#Swap
print LABEL "b: ";print LABEL $swap*2048;print LABEL " 0 swap\n";
print "SWAP size: $swap MB\n";
$offset+=$swap*2048;

#Root
$psize=$srcprt{'/'}/$srcsize*($dstsize-$swap*2048);
if($psize=~/^(\d+)\./){$psize=$1;}
print LABEL "a: $psize $offset 4.2BSD\n";
print "/ size ";print sprintf "%0.0d",$psize/2048;print " MB\n";
$offset+=$psize;

#STD
$letter=0;
%partletter={''};
foreach(@needpartitions)
    {
    $psize=$srcprt{$_}/$srcsize*($dstsize-$swap*2048);
    if($psize=~/^(\d+)\./){$psize=$1;}
    if($letter+1==scalar(@needpartitions))
	{
	$rest=$dstsize-$offset-$psize;
	$psize+=$rest;
	}
    print LABEL "$letters[$letter]: $psize $offset 4.2BSD\n";
    print "$_ size ";print sprintf "%0.0d",$psize/2048;print " MB\n";    
    $offset+=$psize;
    $partletter{$_}=$letters[$letter];
    $letter++;
    }

close(LABEL);

print "---\n";
print "[INF] Last partition were increased for $rest blocks\n";
$pname=$dst."s1";
`$bsdlabel -R -B /dev/$pname ./dstlabels`;
print "[OK] Partitions were created successfully\n";
print "---\n";

if(!-e '/mnt/clone'){mkdir('/mnt/clone');}
push(@needpartitions,'/');
$partletter{'/'}='a';
foreach (@needpartitions)
    {
    `$newfs -O2 -U /dev/$pname$partletter{$_}\n`;
    print "\n[OK] Partition $_ was formatted successfully\n";
    `$mount /dev/$pname$partletter{$_} /mnt/clone`;
    print "Starting dump/restore procedure...\t";
    chdir "/mnt/clone";
    $cansafe=$srcused{$_}*2<$srcprt{$_};
    if($cansafe)
	{
	`$dump -L -f- $_ 2>/dev/null | $restore -rf- 2>/dev/null`;
	print "[OK]\n";
	}
    else
	{
	if($safe)
	    {
	    print STDERR "\nYou must have at least 50% free space on source '$_' partition for clone in safe mode!\n";	
	    }
	else
	    {
	    print STDERR "\n[WARN] Partition $_ moving in unsafe mode!\t";
	    `$dump -f- $_ 2>/dev/null | $restore -rf- 2>/dev/null`;	    
	    print "[OK]\n";
	    }
	}
    
    if($_ eq '/')
	{
	open (LOCKFILE,">/mnt/clone/backup-mode");
	print LOCKFILE "THIS IS BACKUP PARTITION!!!\n Backup date: ";
	print LOCKFILE `$date`;
	close(LOCKFILE);
	
	open (FSTAB,">/mnt/clone/etc/fstab");
	print FSTAB "/dev/$fstab"."s1b\tnone\tswap\tsw\t0\t0\n";
	foreach $buff(@needpartitions)
	    {
	    print FSTAB "/dev/$fstab"."s1$partletter{$buff}\t$buff\tufs\trw\t1\t";
	    if($buff eq '/'){print FSTAB 1;}else{print FSTAB 2;}
	    print FSTAB "\n";
	    }
	close(FSTAB);
	print "[OK] file /etc/fstab generated successfully\n";
	}
    chdir "/";
    $i=1;
    while((system("$umount /mnt/clone"))&&($i<300)){sleep 1;$i++;}
    if($i>1){print "[WARN] Partition $_ was unmounted $i times\n";}
    if($i>=300){print STDERR "Cannot unmount /mnt/clone on $_. Clone script aborted.\n";exit;}
    }
    
rmdir "/mnt/clone";
