diff -urN e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm --- e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm 1969-12-31 16:00:00.000000000 -0800 +++ e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm 2016-01-09 16:32:40.000000000 -0800 @@ -0,0 +1,503 @@ +#---------------------------------------------------------------------- +# Copyright 2015 Ian Wells +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +#---------------------------------------------------------------------- + +package esmith::BlockDevices; + +use strict; +use warnings; +use English '-no_match_vars'; +use Carp; +use File::Path qw(make_path remove_tree); +use POSIX qw(:sys_wait_h strftime); +use Locale::gettext; +use File::stat; +use v5.10.1; +use Taint::Util; +use Readonly; +use File::Find; + +use vars qw($VERSION @ISA @EXPORT_OK); + +@ISA = qw(Exporter); + +=head1 NAME + +esmith::BlockDevices - Module to handle block devices + +=head1 SYNOPSIS + + use esmith::BlockDevices; + my $devices = BlockDevices->new (); + +=head1 DESCRIPTION + +This module provides an abstracted interface to the +block devices used for backup/restore + +=cut + +my $EMPTY = q{}; + +sub new +{ + my $class = shift; + my $self = { + _blox => $EMPTY, + mount => $EMPTY, + _fstype => $EMPTY, + allowmount => $EMPTY, + @_, + }; + bless $self, $class; + return $self; +} + +sub lsblk +{ +#ToDo add some comments + my %blox; # a hash to hold the device information + + my $short = qx(/bin/lsblk -sdn -o KNAME); + my @long = qx(/bin/lsblk -P -b -o KNAME,MAJ:MIN,RM,RO,TYPE,MOUNTPOINT,FSTYPE,LABEL,UUID,MODEL,SIZE,STATE,MODE,OWNER,GROUP); + # Not all of this information may be needed currently, but it does not affect the processing time + untaint ($short); + untaint (@long); + + my $devicemodel= $EMPTY; + + for (@long) + { + my @line = split /\"\s/s; + my $name; + if ($line[0] =~ /KNAME=\"(.*)/s) + { + $name = $1; + } + else {carp 'Could not match KNAME'; last;} # should never occur. + + $blox{$name}{tip} = ($short =~ m/^$name$/sm) ? 1 : 0; + + for (@line) + { + my ($key,$value) = split /=/s; + $value =~ s/\"//sg; + $blox{$name}{$key} = trim($value); + } + if ($blox{$name}{TYPE} =~ /rom|disk/s) + { + $devicemodel = $blox{$name}{MODEL}; + } + else + { + $blox{$name}{MODEL} = trim($devicemodel); + } + $blox{$name}{SIZEH} = scaleIt($blox{$name}{SIZE}); + } + return \%blox; +} + +sub findValidFS +{ +# Find all filesystem types that are supported + my %fs; # a hash to hold the supported filesystem type information + + my @cmd = `cat /proc/filesystems`; + foreach (@cmd) + { + if (/.*\/(.*?)\.ko/s){$fs {$1}=$1;} + } + @cmd = `ls -1 /lib/modules/\$(uname -r)/kernel/fs/*/*ko`; + foreach (@cmd) + { + if (/.*\/(.*?)\.ko/s){$fs {$1}=$1;} + } + return \%fs; +} + +sub scanBlocks +{ + # Scan all the block devices + # This takes some seconds on systems with many filesystems + my ($self) = @_; + $self->{_blox} = lsblk; + $self->{_fstype} = findValidFS; + return; +} + +sub list +{ + my ($self) = @_; + my @dirs=(); + my $hashref = $self->{_blox}; + + foreach my $drive (keys %{$hashref}) + { + push @dirs, $drive; + } + + return @dirs; +} + +sub checkBackupDriveSize +{ + my ($self,$drive, $size) = @_; + my $hashref = $self->{_blox}; + my $sz = $EMPTY; + my $mntdir = $self->{mount}; + Readonly my $VFAT_LIMIT => 2147483648; + Readonly my $KBYTE => 1024; + + # size > drive size + if ($size > $hashref->{$drive}{SIZE}) + { + return 1; # filesystem too small + } + + # FAT32 drive and size > 2G + if (($size > $VFAT_LIMIT) && ($hashref->{$drive}{FSTYPE} eq 'vfat')) + { + return 2; # filesystem vfat limit + } + +#ToDo add a check here to see if mounting is allowed by db value + + # check mount and find actual size + if ($self->mountable ($drive)) # Only check filesystems that appear mountable + { + $self->mount ($drive); + my $filesize = -s "$mntdir/smeserver.tgz"; + + # Check free disk space + my $df = qx(/bin/df -P \"$mntdir\"); + if ($df =~ /(\S+)\s+(\S+)\s+(\S+)\s+(\d*%)/s) + { + my $dsize = ($3 * $KBYTE) + ($filesize //= 0); + if ($size > $dsize) # not enough space + { + $sz = 3; # filesystem has too little free space + } + } + else # fail (never seen in testing) + { + $sz = 4; # Failed to get disk size + } + $self->unmount; + } + return $sz; +} + +# Check each block device +# Return two arrays, valid drives, invalid drives +sub checkBackupDrives +{ + my ($self,$bsize) = @_; + my @valid = (); + my @invalid = (); + $self->scanBlocks; # scan all block devices + my $hashref = $self->{_blox}; + my $allowmount = $self->{'allowmount'}; # Are mounted drives allowed in $checks. + my $checks = 'UU RO FS'; # These checks are always valid + $checks .= ' MO' if ($allowmount eq 'enabled'); + $checks .= ' SZ' if ($bsize); # Only run the size check when a valid size is given + + foreach my $drive (keys %{$hashref}) + { + $hashref->{$drive}{REASON} = $EMPTY; # Reason for a filesystem being (in)valid + next unless $hashref->{$drive}{tip}; #Ignore drives that have child filesystems + + # drives mounted on /, /boot, or [SWAP] are never valid for backups + next if ($hashref->{$drive}{MOUNTPOINT} =~ /^\/boot$|^\[SWAP\]$|^\/$/s ); + + # validate each filesystem against the checks + foreach my $check (split / /s, $checks) + { + for ($check) + { + if (/^UU/si) # No UUID + { + $hashref->{$drive}{REASON} .='UU ' unless $self->uuid ($drive); last; + } + if (/^RO/si) # Read Only + { + $hashref->{$drive}{REASON} .='RO ' if $self->readonly ($drive); last; + } + if (/^FS/si) # Invalid filesystem + { + $hashref->{$drive}{REASON} .='FS ' unless $self->validFS ($drive); last; + } + if (/^MO/si) # Mounted + { + $hashref->{$drive}{REASON} .='MO ' if $self->mountpoint ($drive); last; + } + if (/^SZ/si) # filesystem size, this includes mounting to check free space + { + $hashref->{$drive}{REASON} .='SZ ' if $self->checkBackupDriveSize ($drive, $bsize); + #ToDo the return value contains the reason why there is insufficient space, but this is not used yet. + last; + } + { carp "not supported yet in checkBackupDrives: $check"; } # Should never be seen + } + } + if ($hashref->{$drive}{REASON}) + { + push @invalid, $drive; + } + else + { + push @valid, $drive; + } + } + return (\@valid, \@invalid); +} + +sub findBackup +{ + my ($self, $kname, $foundref, $maxDepth, $count) = @_; + my $hashref = $self->{_blox}; + my $mountpoint = $self->{'mount'}; + my $file = 'smeserver.tgz'; + + $self->mount ($kname); + sleep 1; + + # start with the absolute path + my $findRoot = Cwd::realpath($mountpoint); + + # determine the depth of our beginning directory + my $begDepth = 1 + grep { length } File::Spec->splitdir($findRoot); + + find ( + { + preprocess => sub + { @_ if (scalar File::Spec->splitdir($File::Find::dir) - $begDepth) <= $maxDepth }, + wanted => sub + { + if (($_ =~ m/^$file/s) && ($File::Find::name =~ qr|^([-+@\w\s./:\\]+)$| )) # if matching the backup name + { + $$count++; + my $sb = stat $1; + ${$foundref}{$$count}{count}=$$count; + ${$foundref}{$$count}{device}=$kname; + ${$foundref}{$$count}{path} = $1; + ${$foundref}{$$count}{path} =~ s/$mountpoint//; #strip off the mountpoint + ${$foundref}{$$count}{path} =~ s/$file//; #strip off the filename + ${$foundref}{$$count}{size}=$sb->size; # size in bytes + ${$foundref}{$$count}{sizeH}=scaleIt($sb->size); # human readable size + ${$foundref}{$$count}{time}=strftime '%d %b %g %H:%M', localtime $sb->mtime; + } + }, + untaint => 1, + untaint_pattern => qr|^([-+@\w\s./:\\]+)$|, + untaint_skip =>1, + }, + $findRoot + ); + + $self->unmount; + return; +} + +sub desc # brief description of a filesystem +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + + my $model = $hashref->{$kname}{MODEL}; + my $label = $hashref->{$kname}{LABEL} || gettext('no label'); + my $size = $hashref->{$kname}{SIZEH}; + + return "$label $model $size"; +} + + +# Given the KNAME check if the filesystem.could be mountable +# Check that there are no children, i.e. a tip +# Check that it has a UUID, Filesystem, +sub mountable +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{tip} && $hashref->{$kname}{UUID} && _isFS ($hashref->{$kname}{FSTYPE})) ? 1 : $EMPTY; +} + +# Given the KNAME check if the filesystem.is read-only +# returns 1 for Read-Only and $EMPTY for R-W +sub readonly +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{RO}) ? 1 : $EMPTY; +} + +sub mountpoint +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{MOUNTPOINT}); +} + +sub uuid +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{UUID}); +} + +sub model +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{MODEL}); +} + +# Given the KNAME return the label +# returns 'no label' if none found +sub label +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{LABEL}) || gettext('no label'); +} + +sub size +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{SIZE}); +} + +# Given a filesystem.(eg sr0) check if it's filesystem type is allowed +sub validFS +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + my $fsref = $self->{_fstype}; + return ($fsref->{$hashref->{$kname}{FSTYPE}}) || $EMPTY; +} + +# Given a filesystem.type (eg vfat) check if it is allowed +sub _isFS +{ + my ($filesystem) = @_; + return $EMPTY unless $filesystem; + + my $fsref = findValidFS; + return ($fsref->{$filesystem}) || $EMPTY; +} + +# Return the reason string which indicates why a drive is (in)valid +sub reason +{ + my ($self,$kname) = @_; + my $hashref = $self->{_blox}; + return ($hashref->{$kname}{REASON}); +} + +# Given the KNAME mount the filesystem, example +# system ('/bin/mount', '-t', 'vfat', '-U', '9891-4C8A', '/tmp/mnt'); +sub mount +{ + my ($self, $kname) = @_; + my $hashref = $self->{_blox}; + + $self->createMountpoint; + + system ('/bin/mount', '-t', $hashref->{$kname}{FSTYPE}, '-U', $hashref->{$kname}{UUID}, $self->{mount}) == 0 + or croak (gettext('Failed to mount')." $self->{mount},$hashref->{$kname}{FSTYPE},$hashref->{$kname}{UUID}: $?"); + return; +} + +# Unmount the block device +sub unmount +{ + my $self = shift; + system('/bin/umount', $self->{mount}) == 0 + or croak (gettext('Failed to unmount')." $self->{mount}: $?"); + return; +} + +# Create the mountpoint directory +# Error if already mounted +sub createMountpoint +{ + my $self = shift; + my $mount = $self->{mount}; + + # Check if the mountpoint is in use + if (!checkMount ($mount)) + { + # Try to unmount, will die if fails + $self->unmount; + } + + if ($mount && ! -d $mount) + { + eval {make_path($mount)}; + croak (gettext('Error while creating')." $mount $EVAL_ERROR".gettext('Maybe insufficient permissions.')) if $EVAL_ERROR; + } + return; +} + +sub destroy +{ +# cleanup, unmount and remove mountpoint + + my $self = shift; + my $mount = $self->{mount}; + + + # Check if the mountpoint is in use + if (!checkMount ($mount)) + { + $self->unmount; + } + + if ($mount && -d $mount) + { + eval {remove_tree($mount)}; + croak (gettext('Error while deleting')." $mount $EVAL_ERROR") if $EVAL_ERROR; + } + return; +} + + +### The following subroutines are not specific to block devices +sub scaleIt { + Readonly my $KBYTE => 1024; + my( $size, $n ) =( shift, 0 ); + ++$n and $size /= $KBYTE until $size < $KBYTE; + if ($size >= 1000){++$n ; $size /= $KBYTE;} + return sprintf "%.3g %s", + $size, ( qw[ bytes KB MB GB TB] )[ $n ]; +} + +sub checkMount +{ + # check if $mountdir is mounted + my $mountdir = shift; + $|=1; # Auto-flush + + # copy STDOUT to another filehandle + open (my $STDOLD, '>&', STDOUT); + + open(STDOUT, ">/dev/null"); + if ( open(MOUNTDIR, "|-", "/bin/findmnt", $mountdir)){;} + + # restore STDOUT + open (STDOUT, '>&', $STDOLD); + + return (!close(MOUNTDIR)); +} + +# remove leading and trailing spaces from a string +# this should be moved to a util library. +sub trim +{ + my ($string) = @_; + $string =~ s/^\s+|\s+$//g; + return $string; +} +1; diff -urN e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/console/perform_backup.pm e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/console/perform_backup.pm --- e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/console/perform_backup.pm 2016-02-06 18:23:27.978666800 -0800 +++ e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/console/perform_backup.pm 2016-01-09 16:32:40.000000000 -0800 @@ -6,8 +6,14 @@ use esmith::util; use Locale::gettext; use esmith::Backup; -#use Filesys::DiskFree; -#use Sys::Filesystem; +use Carp; +use feature qw( say ); +use esmith::BlockDevices; +use POSIX qw(:sys_wait_h strftime); +use File::stat; +use Taint::Util; + +my $EMPTY = q{}; sub new { @@ -21,7 +27,7 @@ } sub name -{ +{ return $_[0]->{name}; } @@ -42,22 +48,10 @@ return sub { my $fh = shift; my @backup_list = esmith::Backup->restore_list; - - unless (open(DU, "-|")) - { - open(STDERR, ">/dev/null"); - exec qw(/usr/bin/du -sb), map { "/$_" } @backup_list; - } - my $backup_size = 0; - while () - { - next unless (/^(\d+)/); - $backup_size += $1; - } - close DU; + my $backup_size = backupSize (@backup_list); open(OLDSTDOUT, ">&STDOUT"); - unless (open(STDOUT, ">/mnt/bootstrap-console-backup/smeserver.tgz")) + unless (open(STDOUT, ">$device/smeserver.tgz")) { return gettext("Could not create backup file on device").": $!\n"; } @@ -74,7 +68,7 @@ my $status = 0; my $gzip = open(GZIP, "|-"); - return "could not run gzip" unless defined $gzip; + return "could not run gzip" unless defined $gzip; unless ($gzip) { close $fh; @@ -127,140 +121,130 @@ { my ($self, $console, $db) = @_; my @backup_list = esmith::Backup->restore_list; + my $compressionLevel = $db->get_prop('backupconsole', 'CompressionLevel') || '-6'; + my $mountpoint = $db->get_prop('backupconsole', 'Mountpoint') || '/mnt/bootstrap-console-backup'; + my $allowMounted = $db->get_prop('backupconsole', 'AllowMounted') || 'disabled'; ### For future use $ENV{PATH} = "/bin:/usr/bin"; $ENV{HOME} = "/root"; - my $compressionLevel = $db->get_prop("backupconsole", "CompressionLevel") || "-6"; + my $devices = esmith::BlockDevices->new ('mount' => $mountpoint, 'allowmount' => $allowMounted); + + INITIATE_BACKUP: my ($rc, $choice) = $console->yesno_page ( - title => gettext("Create Backup to removable media"), + title => gettext('Create Backup to removable media'), defaultno => 1, - text => - gettext("Do you wish to create backup on removable media?"), - ); - return unless $rc == 0; - INITIATE_BACKUP: - ($rc, $choice) = $console->yesno_page - ( - title => gettext("Insert media to use for backup"), - left => gettext("Next"), - right => gettext("Cancel"), - text => - gettext("Insert removable media, then hit the enter key."), - ); - return unless $rc == 0; - sleep(3); - my @dirs = (); - my @labels = (); - foreach my $udi (qx(hal-find-by-property --key volume.fsusage --string filesystem)) { - $udi =~ m/^(\S+)/; - - my $is_readonly = qx(hal-get-property --udi $1 --key volume.is_mounted_read_only); - - if ($is_readonly eq "false\n") { - - my $is_mounted = qx(hal-get-property --udi $1 --key volume.is_mounted); - - if ($is_mounted eq "false\n") { - my $blkdev = qx(hal-get-property --udi $1 --key block.device); - $blkdev =~ m/^(\S+)/; - push @dirs, $1; - } - if ($is_mounted eq "false\n") { - my $vollbl = qx(hal-get-property --udi $1 --key volume.label); - $vollbl =~ m/^(\S+)/; - if ($vollbl =~ /^\s/) {$vollbl = 'nolabel';} - chomp $vollbl; - push @labels, lc($vollbl); - } - } - } - unless ($dirs[0]) + text => gettext('Do you want to create a backup on removable media?')."\n\n". + gettext('Insert removable media before proceeding.')."\n". + gettext('It may take many seconds to scan for media.'), + ); + if ($rc != 0) { - ($rc, $choice) = $console->message_page - ( - title => gettext("Writable backup medium not found"), - right => gettext("Back"), - text => - gettext("No removable and/or writable media or device found"), - ); - goto INITIATE_BACKUP; + $devices->destroy; + return; } - mkdir("/mnt/bootstrap-console-backup"); - my $device = $dirs[0]; - if (defined $dirs[1]) - { - my $count=1; - my @args = map { $count++ . '.' => $_ } @dirs; + ### determine which filesystems are valid or not for backups + # check expected backup size + my $backup_size = backupSize (@backup_list); - my ($rc, $choice) = $console->menu_page - ( - title => gettext("Choose device to use for backup"), - text => ("@dirs \n @labels"), - argsref => \@args, - left => gettext("Cancel"), - right => gettext("OK"), - ); - goto INITIATE_BACKUP unless ($rc == 0); - my %args_hash = ( @args ); - $device = $args_hash{$choice}; - } - unless ( system("/bin/mount", "$device", "/mnt/bootstrap-console-backup") == '0' ) + # validate each filesystem + my ($valid, $invalid) = $devices->checkBackupDrives ($backup_size); + my $text = $EMPTY; + + if (${$invalid}[0]) # If there are filesystems that are not valid. { - ($rc, $choice) = $console->message_page - ( - title => gettext("No mountable backup medium"), - right => gettext("Back"), - text => - gettext("Unable to mount removable media, please check the file system format (default : Vfat,Ext2,Ext3,Ext4)"), - ); - goto INITIATE_BACKUP; + $text .= gettext ('These filesystems are not valid:')."\n"; + foreach my $drive (sort @{$invalid}) + { + $text .= "$drive ".$devices->desc($drive).' '.gettext ('Reason').': '.$devices->reason($drive)."\n"; + } + $text .= "\n"; } - use File::stat; - my $st = stat("/mnt/bootstrap-console-backup/smeserver.tgz"); - if ($st) + unless (${$valid}[0]) # Unless a device is found show error page { -# TODO -# old backup exists - what do we want to do with it? - my $size = $st->size; - } + my $title = gettext('No valid backup device found').' '.gettext('size').' '.esmith::BlockDevices::scaleIt($backup_size); + $text .= "\n$title, ".gettext('please try again'); + ($rc, $choice) = $console->yesno_page + ( + title => $title, + text => $text, + left => gettext('Try again'), + right => gettext('Cancel'), + ); + if ($rc == 0) # Try Again + { + goto INITIATE_BACKUP; + } + else + { + $devices->destroy; + return; + } + } + $text .= gettext ('The following are valid for backup').' '; + $text .= gettext ('size').' '.esmith::BlockDevices::scaleIt($backup_size); + #ToDo when valid + invalid > 13 then may need to limit the information + my @args = map { $_ => $devices->desc($_) } @{$valid}; + + # Display the available backup destinations. + ($rc, $choice) = $console->menu_page + ( + title => gettext('Choose device to use for backup').' '.gettext('size').' '.esmith::BlockDevices::scaleIt($backup_size), + text => $text, + argsref => \@args, + left => gettext('Cancel'), + right => gettext('OK'), + ); + goto INITIATE_BACKUP unless ($rc == 0); + untaint $choice; + $devices->mount ($choice); # mount the chosen filesystem + $console->infobox( - title => gettext("Preparing for backup"), - text => gettext("Please stand by while the system is prepared for backup..."), + title => gettext('Preparing for backup'), + text => gettext('Please stand by while the system is prepared for backup...'), ); - my $backup_size = 0; - system("/sbin/e-smith/signal-event", "pre-backup"); + system("/sbin/e-smith/signal-event", 'pre-backup'); + + $console->gauge(make_backup_callback($mountpoint,$compressionLevel), 'title' => gettext('Creating backup file')); + + $devices->destroy; + + system("/sbin/e-smith/signal-event", 'post-backup'); + $console->message_page + ( + title => gettext('Backup complete'), + text => gettext('Remove backup media.'), + ); + return; +} + + +sub backupSize +{ + my $size; + unless (open(DU, "-|")) { open(STDERR, ">/dev/null"); - exec qw(/usr/bin/du -sb), map { "/$_" } @backup_list; + exec qw(/usr/bin/du -sb), map { "/$_" } @_; } while () { next unless (/^(\d+)/); - $backup_size += $1; + $size += $1; } close DU; - - $console->gauge(make_backup_callback("/mnt/bootstrap-console-backup",$compressionLevel), 'title' => gettext("Creating backup file")); - - system("/bin/umount", "/mnt/bootstrap-console-backup"); - rmdir("/mnt/bootstrap-console-backup"); - system("/sbin/e-smith/signal-event", "post-backup"); - ($rc, $choice) = $console->message_page - ( - title => gettext("Backup complete"), - text => - gettext("Remove removable media, then hit the enter key."), - ); + + return $size; } #use esmith::console; #esmith::console::perform_backup->new->doit(esmith::console->new, # esmith::ConfigDB->open); 1; + diff -urN e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/console/perform_restore.pm e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/console/perform_restore.pm --- e-smith-backup-2.4.0.old/root/usr/share/perl5/vendor_perl/esmith/console/perform_restore.pm 2016-02-06 18:23:27.839666800 -0800 +++ e-smith-backup-2.4.0/root/usr/share/perl5/vendor_perl/esmith/console/perform_restore.pm 2016-01-09 16:32:40.000000000 -0800 @@ -4,6 +4,12 @@ use esmith::ConfigDB; use esmith::console; use Locale::gettext; +use Carp; +use feature qw( say ); +use esmith::BlockDevices; +use Taint::Util; + +my $EMPTY = q{}; sub new { @@ -16,9 +22,8 @@ return $self; } - sub name -{ +{ return $_[0]->{name}; } @@ -30,124 +35,154 @@ sub doit { my ($self, $console, $db) = @_; - if ($db->get_prop('bootstrap-console', 'Run') eq 'yes') # called from bootstrap console + my $compressionLevel = $db->get_prop('backupconsole', 'CompressionLevel') || '-6'; + my $mountpoint = $db->get_prop('backupconsole', 'Mountpoint') || '/mnt/bootstrap-console-backup'; + my $allowMounted = $db->get_prop('backupconsole', 'AllowMounted') || 'disabled'; ### For future use + my $restoreMaxDepth = $db->get_prop('backupconsole', 'MaxDepth') || 1; ### For future use + my %found; + my $backupcount = 0; + my $backupdrive; # Which filesystem holds the backup + my $backupfile; # full path to the backup + my ($time, $size); # time and size of chosen backup + + if ($db->get_prop('bootstrap-console', 'Run') eq 'yes') # called from bootstrap console { return if ($db->get_value('PasswordSet') eq 'yes'); # too late } return if ($db->get_prop('bootstrap-console', 'Restore') eq 'disabled'); + + my $devices = esmith::BlockDevices->new ('mount' => $mountpoint, 'allowmount' => $allowMounted); + + INITIATE_RESTORE: my ($rc, $choice) = $console->yesno_page ( title => gettext("Restore From Backup"), defaultno => 1, - text => - gettext("Do you wish to restore from backup?"), - ); - return unless $rc == 0; - mkdir("/mnt/bootstrap-console-backup"); - system("/etc/init.d/messagebus", "start"); - system("/etc/init.d/haldaemon", "start"); - INITIATE_RESTORE: - ($rc, $choice) = $console->yesno_page - ( - title => gettext("Insert media containing backup"), - left => gettext("Next"), - right => gettext("Cancel"), - text => - gettext("Insert removable media containing your backup file, then hit the enter key."), - ); - unless ($rc == 0) { - system("/etc/init.d/haldaemon", "stop"); - system("/etc/init.d/messagebus", "stop"); - rmdir("/mnt/bootstrap-console-backup"); + text => gettext('Do you wish to restore from backup?')."\n\n". + gettext('Insert removable media before proceeding.')."\n". + gettext('It may take many seconds to scan for media.'), + ); # Buttons are Yes & No + if ($rc != 0) # choice was not Yes + { + $devices->destroy; return; } - sleep(3); - my @dirs; - @dirs = (); - foreach my $udi (qx(hal-find-by-property --key volume.fsusage --string filesystem)) { - $udi =~ m/^(\S+)/; - my $is_mounted = qx(hal-get-property --udi $1 --key volume.is_mounted); - - if ($is_mounted eq "false\n") { - my $blkdev = qx(hal-get-property --udi $1 --key block.device); - $blkdev =~ m/^(\S+)/; - push @dirs, $1; + + ### determine which filesystems are valid or not for backups + + # validate each filesystem + my ($valid, $invalid) = $devices->checkBackupDrives ($EMPTY); + my $text = $EMPTY; + + if (${$valid}[0]) # There are filesystems that could hold a backup. + { + $text .= gettext ('These filesystems could hold backups')."\n"; + foreach my $drive (sort @{$valid}) + { + $text .= "$drive ".$devices->desc($drive)."\n"; + $devices->findBackup ($drive, \%found, $restoreMaxDepth, \$backupcount); } + $text .= "\n"; } - unless ($dirs[0]) + + unless ($backupcount) # Unless a valid backup is found show error page { - ($rc, $choice) = $console->message_page - ( - title => gettext("Backup medium not found"), - right => "Try again", - text => - gettext("No removable media or device found"), - ); - goto INITIATE_RESTORE; - } - my $device = $dirs[0]; - if (defined $dirs[1]) - { - my $count=1; - # FIXME use better regexp - my @args = map { /(.*)/; $count++ . '.' => $1 } @dirs; + if (${$invalid}[0]) # If there are filesystems that are not valid. + { + $text .= gettext ('These filesystems are not valid:')."\n"; + foreach my $drive (sort @{$invalid}) + { + $text .= "$drive ".$devices->desc($drive).' '.gettext ('Reason').': '.$devices->reason($drive)."\n"; + } + $text .= "\n"; + } + my $title = gettext('No valid backup device found'); + $text .= "\n$title, ".gettext('please try again'); + ($rc, $choice) = $console->yesno_page + ( + title => $title, + text => $text, + left => gettext('Try again'), + right => gettext('Cancel'), + ); + if ($rc == 0) # Try Again + { + goto INITIATE_RESTORE; + } + else + { + $devices->destroy; + return; + } + } - my ($rc, $choice) = $console->menu_page - ( - title => gettext("Choose device to restore from"), - text => gettext("Please select which device contains the backup file you wish to restore from."), - argsref => \@args, - left => gettext("Cancel"), - right => gettext("OK"), - ); + # %found contains $backupcount backups. + if ($backupcount == 1) + { + # One backup found, so simple yes/no choice + $backupdrive = $found{1}{device}; # Find the (only) device that a backup was found on + $backupfile = $found{1}{path}; # find the actual backup + $time = gettext('Date') .' '. $found{1}{time}; + $size = gettext('Size') .' '. $found{1}{sizeH}; + + ($rc, $choice) = $console->yesno_page + ( + title => gettext('Start restore from backup'), + text => + gettext('Backup found on device'). + "\n$backupdrive ".$devices->desc($backupdrive)."\n\n". + gettext('Backup details'). + "\n$backupfile $size $time\n\n\n". + gettext('Do you wish to restore from this file?'), + ); goto INITIATE_RESTORE unless ($rc == 0); - my %args_hash = ( @args ); - $device = $args_hash{$choice}; + $size = $found{1}{size}; } - system("/bin/mount", "$device", "/mnt/bootstrap-console-backup"); - sleep(1); - - unless (-f "/mnt/bootstrap-console-backup/smeserver.tgz") + else # Multiple backups found so display a choice { - system("/bin/umount", "$device"); - ($rc, $choice) = $console->message_page - ( - title => gettext("Backup file not found"), - right => "Try again", - text => - gettext("No backup file found"), - ); - goto INITIATE_RESTORE; - } - use File::stat; - my $st = stat("/mnt/bootstrap-console-backup/smeserver.tgz"); - my $size = $st->size; - - ($rc, $choice) = $console->yesno_page - ( - title => gettext("Start restore from backup"), - text => - gettext("Backup file found:") . " smeserver.tgz ($device) " . - gettext("size") . " $size " . gettext("bytes") . - "\n\n" . - gettext("Do you wish to restore from this file?"), - ); - unless ($rc == 0) { - system("/bin/umount", "$device"); - goto INITIATE_RESTORE; + $text = gettext ('Backups found on these devices')."\n"; + foreach my $backupfound (sort keys %found) + { + $backupdrive = $found{$backupfound}{device}; + if (($backupfound == 1) || ($found{$backupfound}{device} ne $found{$backupfound-1}{device})) + { + $text.= "$backupdrive ".$devices->desc($backupdrive)."\n"; + } + } + my @args = map { $_ => "$found{$_}{device} $found{$_}{path} $found{$_}{sizeH} $found{$_}{time}" } sort keys %found; + ($rc, $choice) = $console->menu_page + ( + title => gettext('Start restore from backup'), + text => + "$text\n". + gettext ('Please select the backup that you wish to restore from.'), + argsref => \@args, + left => gettext('Cancel'), + right => gettext('OK'), + ); + goto INITIATE_RESTORE unless ($rc == 0); + untaint $choice; + $backupdrive = $found{$choice}{device}; + $size = $found{$choice}{size}; } + + $devices->mount ($backupdrive); # mount the chosen filesystem + sleep(1); # Some mounts take time to become active + + ###ToDo This section has no error checking + ###ToDo 'Restoring data is not localized + # Execute the restore system("/sbin/e-smith/signal-event", "pre-restore"); - system("(cd / ; cat /mnt/bootstrap-console-backup/smeserver.tgz | + system("(cd / ; cat $mountpoint/smeserver.tgz | pv -n -s $size | gunzip | tar xf - > /dev/null ) 2>&1 | dialog --backtitle 'Restoring data' --guage 'Progress' 7 70"); - system("/bin/umount", "$device"); - system("/etc/init.d/haldaemon", "stop"); - system("/etc/init.d/messagebus", "stop"); - rmdir("/mnt/bootstrap-console-backup"); + + # Restore complete, now clean-up + $devices->destroy; system("/sbin/e-smith/signal-event", "post-upgrade"); - + unless ( $self->{bootstrap} ) { $db->set_prop("bootstrap-console", "Run", "yes");