#!/usr/bin/perl
use warnings;
use strict;
use Simba::CA;
use POSIX qw(strftime);
use Getopt::Long;
use Filesys::Statvfs;
use File::stat;

my @filesets;

my $parallel;
GetOptions(
    'filesets=i' => \@filesets,
    'parallel=i' => \$parallel
);
@filesets = split(/,/,join(',',@filesets));

$ENV{PATH} = "/usr/bin";

my $now = strftime('%Y-%m-%dT%H:%M:%S', localtime());
open(my $log, '>>', '/var/log/simba/ca.log.' . $now);
$log->autoflush(1);

my $ca = Simba::CA->new({
			    dbi_file => $ENV{SIMBA_DB_CONN} || "$ENV{HOME}/.dbi/simba",
			    fh_log   => $log,
			    (@filesets ? ( filesets => \@filesets ) : ()),
                            parallel => $parallel,
			});
$ca->log_level(9);

# Try to find all devices suitable for backup and mount them.
# We do this by trying to find a matching device for each subdirectory
# of /backup. Another way might be to check all USB disks.
my $st = stat("/backup/");
my $base_device = $st->dev;
my %luks_devices;
for (glob("/backup/*")) {
    my $st = stat($_);
    my $dir_device = $st->dev;
    $ca->log(0, "checking $_");
    if ($base_device == $dir_device) {
        # not a mount point
        (my $basedir = $_) =~ s{^/backup/}{};
        if ($basedir =~ /^luks-(.*)/) {
            my $key = $1;
            for my $dev (glob("/dev/disk/by-id/*$key*")) {
                my ($devbase) = $dev =~ m{([^/]+$)};
                if (-e "/backup/keys/$devbase") {
                    $ca->log(0, "opening /dev/disk/by-id/$devbase on $_");
                    system("/sbin/cryptsetup", "open", $dev, $basedir, "--key-file", "/backup/keys/$devbase");
                    $ca->log(0, "mounting /dev/mapper/$basedir on $_");
                    system("/bin/mount", "-o", "nodev,noexec,nomand,nosuid", "/dev/mapper/$basedir", $_);
                }
            }
        } elsif (-e "/dev/disk/by-id/$basedir") {
            # matching device exists
	    $ca->log(0, "mounting /dev/disk/by-id/$basedir on $_");
            system("/bin/mount", "-o", "nodev,noexec,nomand,nosuid", "/dev/disk/by-id/$basedir", $_);
        }
    }
}


# Choose a random backup directory.
# The directories are weighted by free space (e.g, if we have two
# directories with 3 TB and 2 TB free space, then their chances of being
# chosen are 60% and 40%).

my @backup_dirs = map {
			$_ = $1 if m{(.*)/.*}; # basedir and detaint
			my($bsize, $frsize, $blocks, $bfree, $bavail,
			   $files, $ffree, $favail, $flag, $namemax)
			    = statvfs($_);
			my $available_bytes = $bsize * $bavail;
			my $avg_file_size = $bavail * ($blocks - $bfree) / ($files - $ffree);
			my $available_bytes_by_files = $avg_file_size * $favail;
			$available_bytes = $available_bytes_by_files if $available_bytes_by_files < $available_bytes;
                        $ca->log(3, "found base $_ (est. $available_bytes bytes)");
			[ $_, $available_bytes ]
                      } glob("/backup/*/active");
my $sum_free = 0;
$sum_free += $_->[1] for (@backup_dirs);
$ca->log(3, "total free est. $sum_free bytes");
my $rnd = rand();
$ca->log(3, "random (raw) = $rnd");
$rnd *= $sum_free;
$ca->log(3, "random (scaled) = $rnd");
my $count_free = 0;
my $backup_dir;
for(@backup_dirs) {
    $count_free += $_->[1];
    $ca->log(3, "considering base $_->[0] (est. $_->[1] bytes)");
    if ($count_free >= $rnd) {
	$backup_dir = $_->[0];
	$ca->log(3, "using base $_->[0]");
	last;
    }
}
unless ($backup_dir) {
    $ca->log(0, "no backup directory found");
    exit(1);
}
$ca->basedir($backup_dir);

# umount all potential backup dirs again, except the one we are actually
# using
for (@backup_dirs) {
    next if $_->[0] eq $backup_dir;
    $ca->log(0, "unmounting $_->[0]");
    system("/bin/umount", $_->[0]);
    if ($_->[0] =~ m{(luks-[^/]+)}) {
        $ca->log(0, "closing $1");
        system("/sbin/cryptsetup", "close", $1)
    }
}
chdir($backup_dir); # prevent accidental umount

$ca->run();

# umount backup dir

chdir("/");
$ca->log(0, "unmounting $backup_dir");
system("/bin/umount", $backup_dir);
if ($backup_dir =~ m{(luks-[^/]+)}) {
    $ca->log(0, "closing $1");
    system("/sbin/cryptsetup", "close", $1)
}