#!/usr/bin/perl
use strict;
use warnings;
use File::Basename;
use Getopt::Std;
use XML::Simple;

#----------------------------------------- CONS --------------------------------
#
my $APL = $ENV{APL};
my $APL_VAR = $ENV{APL_VAR};
my $BACKUP_ROOT = "$APL_VAR/backup";
my $MNT="/vasm";
my $MOUNT_POINT = "/tmp/mnt$$";
my $TMP_LOC = "/tmp/findbackup";
my $SCAN = "sudo $APL/sm/sbin/sm_scan '<all>' ";
my $MOUNT ="sudo $APL/sm/sbin/sm_mount";
my $UMOUNT="sudo $APL/sm/sbin/sm_umount";
my $LIST='/sbin/fdisk -l 2>/dev/null';                # look for USB-flash

#----------------------------------------- VARS --------------------------------
#
my %Opts;
getopts("vn",\%Opts);

my $Verbose = $Opts{v};
my $NoCopy = $Opts{n};
my $BackupID = $ARGV[0] if @ARGV;
my %BackupList;
my $DEBUG = $ENV{DEBUG};

my $Found;

#----------------------------------------- ROUTINES ----------------------------
#
sub read_vctl_xml
{
    	my $vctl = eval { XMLin("$_[0]") };
    	die "Cannot read version information from backup: $@\n" if $@;
    	my %verinfo;
    	foreach my $key (keys %{ $vctl->{INFO} }) {
    	    $verinfo{lc($key)} = $vctl->{INFO}{$key};
    	}
    	return \%verinfo;
}

sub read_verinfo
{
	open FH, "$_[0]" or die "Cannot read version information from backup: $!\n";
    	my %verinfo = map {/^(\w+)=(.*)$/} grep {/^\w+=.*$/} <FH>;
    	close FH;
    	return \%verinfo;
}

sub print_backup
{
	my $id = shift;
	my $bck = $BackupList{$id};
	my $line = '';
	if ($Verbose) {
		$bck->{COMMENT}=~s/\s/_/g if defined $bck->{COMMENT}; #remove spaces
		$line .= "$_=$bck->{$_} " foreach keys %$bck;
		chop $line;
	}
	else {
		my $type = $bck->{TYPE} eq 'A' ? " A" : "  ";
		my $comment = length($bck->{COMMENT}) ? " $bck->{COMMENT}" : "";
		$line .= "$bck->{ID}${type}${comment}";
		$line =~ s/\s*$//;
	}
	print "$line\n";
}

sub copy_backup
{
	my ($path, $id) = @_;
	
	return 1 if $NoCopy;
	return 0 unless -d "$BACKUP_ROOT/master";
	my $dst = "$BACKUP_ROOT/master/$id";
	if (-d $dst) {
		return 1 if `stat -c %i $path` eq `stat -c %i $dst`;
		system("rm -rf $dst");
	}
	system("/bin/cp -rp $path $BACKUP_ROOT/master");
	return 0 if $?;
	system("chown -R $ENV{APL_USR}:$ENV{APL_HTTPD_GRP} $dst");
	return 1;
}

sub unpack_backup
{
	my ($backup_path, $id) = @_;
	
	my $cmd = '';
	my $mime = `/usr/bin/file -bi $backup_path 2>/dev/null`;
	chomp $mime if $mime;
	return 0 unless $mime;
	for ($mime) {
		/^application\/x-zip/  and do { $cmd = "unzip -qq -P X3ins1de"; last };
		/^application\/x-gzip/ and do { $cmd = "tar -xzf"; last };
	}
	return 0 unless $cmd;
	
	`rm -rf $TMP_LOC 2>/dev/null`;
	mkdir($TMP_LOC, 0700);
	`cp $backup_path $TMP_LOC && cd $TMP_LOC && $cmd ${id}.mbck 2>/dev/null`;
	if ($?) {
		`rm -rf $TMP_LOC 2>/dev/null`;
		return 0;
	}
	
	return 1;
}

sub scan_master_backup
{
	my ($path, $id) = @_;
	return if $BackupList{$id};

	my $ok = 1;
	my %bck;
	
	# Check backup integrity
	eval {
		die "Version is missing!\n" if not -f "$path/verinfo" and not -f "$path/vctl.xml";
		die "Backup statistics is missing!\n" unless -f "$path/stat.xml";
		die "DB Configuration info missing in backup\n" unless -d "$path/db";
		
		my %verinfo;
		if (-f "$path/verinfo") {
		    %verinfo = %{ read_verinfo "$path/verinfo" };
		} else {
		    #die "Skip migratory backups unless in Verbose mode\n" unless $Verbose;
		    %verinfo = %{ read_vctl_xml "$path/vctl.xml" };
		}
    		my ($ver,$uni,$activated) = ($verinfo{ver},$verinfo{uni},'Ms');
    		$activated=$verinfo{activated} if defined $verinfo{activated};
    		# check whether backup was created for the current system
		my $stat = eval { XMLin("$path/stat.xml", ForceArray => 1, KeyAttr => ['ID','NAME']) };
		die "Failed to parse backup information\n" if $@;
		die "Not a complete backup\n" if $stat->{STATUS} ne 'OK';
		
		my $comment = '';
		if (open(FH, "$path/comment")) {
		    $comment = <FH>;
		    close FH;
		    chomp $comment;
		    $comment =~ s/^\s*//; $comment =~ s/\s*$//; # trim
		}
		$bck{ID} = $id;
		$bck{COMMENT} = $comment;
		$bck{VER} = $ver;
		$bck{UNI} = $uni;
                $bck{ACTIVATED} = $activated;
		$bck{TYPE} = -f "$path/db/transdb.db" ? "A" : "C"; # A - All, C - config only
		
		$BackupList{$id} = \%bck;
		
	};
	if ($@) {
		$ok = 0;
		warn("[$path] Skipped: $@") if $DEBUG;
	}
	
	return $ok;
}

sub scan_backup_path
{
	my $path = shift;
	
	return unless -d $path;
	$BackupID = basename($path);
	my $dir = dirname($path);
	return unless $BackupID=~/^\d{6}_\d{6}$/;
	
	my $ok = scan_master_backup($path, $BackupID);
	if ($ok) {
		my $dst = "$BACKUP_ROOT/master/$BackupID";
		if (not $Found) {
			$Found = 1 if copy_backup($path, $BackupID);
		}
	}
}

sub scan_volume
{
	my $vol = shift;
	
	my $base   = "$vol/backup";
	my $master = "$base/master";
	return unless -d $base;
	return unless -d $master;
	
	opendir(DH, $master) or next;
	my @ids = grep {/^(\d{6}_\d{6})$/} readdir(DH);
	closedir(DH);
		
	my $ok;
	foreach my $id (@ids) {
		last if $BackupID and $Found;
			
		$ok = scan_master_backup("$master/$id", $id);
		
		if ($BackupID and $id eq $BackupID and $ok) {
			my $dst = "$BACKUP_ROOT/master/$id";
			if (not $Found) {
				$Found = 1 if copy_backup("$master/$id", $id);
			}
		}
	}
}

sub scan_volumes
{
	if ($BackupID) {
		$Found = 1 if -d "$BACKUP_ROOT/master/$BackupID";
	        return if $Found;
	}
	
	open(SCAN, "$SCAN |") || die("Cannot scan volumes: $!");
	my @scan = <SCAN>;
	close SCAN;
	
    	foreach my $line (@scan) {
    		my ($used, $id, $name, $size, $dev, $mnt) = split(/\s+/, $line);
    		if ($used eq 'free') { # Mount volume to 'probe' location
			`$MOUNT $id '' '-o ro' probe >/dev/null 2>&1`;
		        scan_volume("$MNT/probe");
		        `$UMOUNT probe`;
		}
		else {
			scan_volume($mnt);
		}
		
		last if $BackupID and $Found;
	}
	
	scan_volume($APL_VAR) unless $BackupID;
}

sub scan_usb
{
	die "Must be root to scan USB devices\n" if $> != 0;
	
	`umount $MOUNT_POINT 2>/dev/null; rm -rf $MOUNT_POINT`;
	mkdir($MOUNT_POINT, 0700);
	my @list;
	open(LIST,"$LIST 2>/dev/null|") || die "Cannot do $LIST: $@";
	foreach(<LIST>) {
		push(@list,$1) if m|^(/dev/sd\w?\d)\s.+FAT\d?\d?|;
		push(@list,$1) if m|^Disk\s(/dev/sd\w?):\s+(\d\d\d\d?)\sMB|;
	}
	close LIST;
	open(MOUNT,"mount|") || die "Cannot run mount";
	my %mounted= map{m|(/dev/s\w+\d)\son\s(\S+)\s|} grep {m|^/dev/sd\w\d\son\s\S+\s|} <MOUNT>;
	close MOUNT;
	foreach my $drive (@list) {
		my $mpoint = $MOUNT_POINT;
		if (defined $mounted{$drive}) {
			$mpoint = $mounted{$drive};
		}
		else {
			`umount $MOUNT_POINT 2>/dev/null; mount -r $drive $MOUNT_POINT 2>/dev/null`;
			next if $?;                       # mount fails
		}
		opendir(DIR, $mpoint);
		my @ids = map {/^(\d{6}_\d{6})/} grep {/^\d{6}_\d{6}\.mbck$/} readdir DIR;
		closedir DIR;
		next unless @ids;
		foreach my $id (reverse sort @ids) {
			next unless unpack_backup("$mpoint/${id}.mbck", $id);
			next unless scan_master_backup("$TMP_LOC/$id", $id);
			next unless copy_backup("$TMP_LOC/$id", $id);
			$Found = 1;
			$BackupID = $id;
			last;
		}
		last if $Found;
	}
}

sub finalize
{
	unless ($BackupID) {
		foreach my $id (sort keys %BackupList) {
			print_backup($id);
		}
	}
	else {
		my $err = "";
		if ($Found) {
			unless($BackupList{$BackupID}) {
				my $ok = scan_master_backup("$BACKUP_ROOT/master/$BackupID", $BackupID);
				$err = "Invalid backup format" if not $ok;
			}
		}
		else {
			$err = "Unable to find MASTER backup with given ID"
				if $BackupID ne 'USB';
		}
		if ($err) {
			warn "$err\n";
			exit 1;
		}
		else {
			if ($BackupID eq 'USB' and not $Found) {
				print "NOT FOUND\n";
			}
			else {
				print_backup($BackupID);
			}
			exit 0;
		}
	}
}

sub main
{
	if (not $BackupID or $BackupID =~ /^\d{6}_\d{6}$/) {
		scan_volumes;
	}
	elsif ($BackupID eq 'USB') {
		scan_usb;
	}
	else {
		scan_backup_path($BackupID);
	}

	finalize;
}


#----------------------------------------- MAIN --------------------------------
#
main;


END {
	`umount $MOUNT_POINT &>/dev/null; rm -rf $MOUNT_POINT` if -d $MOUNT_POINT;
	`rm -rf $TMP_LOC` if -d $TMP_LOC;
}
