initial commit of file from CVS for e-smith-backup on Thu 26 Oct 11:24:24 BST 2023
This commit is contained in:
37
root/usr/share/locale/en_US/LC_MESSAGES/backup.po
Normal file
37
root/usr/share/locale/en_US/LC_MESSAGES/backup.po
Normal file
@@ -0,0 +1,37 @@
|
||||
#----------------------------------------------------------------------
|
||||
# Copyright 1999-2003 Mitel Networks Corporation
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the same terms as Perl itself.
|
||||
#----------------------------------------------------------------------
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2003-03-06 14:53-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: root/etc/e-smith/templates/etc/cron.d/backup:40
|
||||
msgid "Remember to load the backup tape!"
|
||||
msgstr ""
|
||||
|
||||
#: root/etc/e-smith/templates/etc/cron.d/backup:47
|
||||
msgid "Backup task is disabled"
|
||||
msgstr ""
|
||||
|
||||
#: root/etc/e-smith/templates/sbin/e-smith/backup/05check-enabled:7
|
||||
msgid "Tape backups are enabled"
|
||||
msgstr ""
|
||||
|
||||
#: root/etc/e-smith/templates/sbin/e-smith/backup/05check-enabled:12
|
||||
msgid "Tape backups have been disabled"
|
||||
msgstr ""
|
||||
|
||||
#: root/etc/e-smith/templates/sbin/e-smith/backup/20probe-tape:22
|
||||
msgid "Backup aborted: No tape loaded"
|
||||
msgstr ""
|
574
root/usr/share/perl5/vendor_perl/esmith/Backup.pm
Normal file
574
root/usr/share/perl5/vendor_perl/esmith/Backup.pm
Normal file
@@ -0,0 +1,574 @@
|
||||
#----------------------------------------------------------------------
|
||||
# Copyright 1999-2007 Mitel Networks Corporation
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the same terms as Perl itself.
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
package esmith::Backup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Copy;
|
||||
use Unix::PasswdFile;
|
||||
use Passwd::Unix;
|
||||
use esmith::lockfile;
|
||||
|
||||
use vars qw($VERSION @ISA @EXPORT_OK);
|
||||
|
||||
use constant ESMITH_RESTORE_CACHE => '/var/cache/e-smith/restore';
|
||||
use constant ESMITH_BACKUP_LOCK_FILE => "/var/lock/subsys/backup-running";
|
||||
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
#path to *.include/*.exclude files
|
||||
my $BackupDir = '/etc/backup-data.d/';
|
||||
|
||||
=head1 NAME
|
||||
|
||||
esmith::Backup - interface to server backup/restore information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# When you want to add or remove files/folders
|
||||
# in your backup, make files in /etc/backup-data.d
|
||||
# with one or several path name inside (one per line)
|
||||
# eg : /opt/myContrib
|
||||
# /usr/share/myContrib
|
||||
#
|
||||
# FileName.include -> add more files/folders
|
||||
# FileName.exclude -> remove files/folder
|
||||
|
||||
use esmith::Backup;
|
||||
my $backup = new esmith::Backup;
|
||||
|
||||
#retrieve the list of your backup exclusions
|
||||
my @backup_excludes = $backup->excludes;
|
||||
#or
|
||||
my @backup_excludes = esmith::Backup->excludes;
|
||||
|
||||
# retrieve the list of your backup inclusions
|
||||
my @backup_list = $backup->restore_list;
|
||||
#or
|
||||
my @backup_list = esmith::Backup->restore_list;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module provides an abstracted interface to the backup/restore
|
||||
information
|
||||
|
||||
=cut
|
||||
|
||||
=begin testing
|
||||
|
||||
use esmith::TestUtils qw(scratch_copy);
|
||||
use_ok("esmith::Backup");
|
||||
|
||||
$backup = new esmith::Backup;
|
||||
isa_ok($backup, 'esmith::Backup');
|
||||
|
||||
=end testing
|
||||
|
||||
=head2 new
|
||||
|
||||
This is the class constructor.
|
||||
|
||||
=cut
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = ref($_[0]) || $_[0];
|
||||
my $self = {};
|
||||
$self = bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
=head2 restore_list
|
||||
|
||||
Returns an (ordered) array of files/directories to recover from the
|
||||
backup. The pathnames are relative to root.
|
||||
|
||||
=cut
|
||||
|
||||
sub restore_list
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
my @backup = (
|
||||
'home/e-smith',
|
||||
'etc/e-smith/templates-custom',
|
||||
'etc/e-smith/templates-user-custom',
|
||||
'etc/ssh',
|
||||
'root',
|
||||
'etc/sudoers',
|
||||
'etc/passwd',
|
||||
'etc/shadow',
|
||||
'etc/group',
|
||||
'etc/gshadow',
|
||||
'etc/samba/secrets.tdb',
|
||||
'etc/samba/smbpasswd',
|
||||
'etc/samba/schannel_store.tdb',
|
||||
'etc/backup-data.d',
|
||||
'var/lib/samba/group_mapping.tdb',
|
||||
'var/lib/samba/account_policy.tdb',
|
||||
);
|
||||
#add all paths in .include files
|
||||
push @backup, $self->includes;
|
||||
return @backup;
|
||||
}
|
||||
|
||||
|
||||
=head2 uniq
|
||||
|
||||
Remove all duplicates from the given array.
|
||||
|
||||
=cut
|
||||
|
||||
sub uniq {
|
||||
return keys %{{ map { $_ => 1 } @_ }};
|
||||
}
|
||||
|
||||
|
||||
=head2 load_file_list
|
||||
|
||||
head2 load_file_list
|
||||
Given a file name, return all lines in an array.
|
||||
|
||||
=cut
|
||||
|
||||
sub load_file_list
|
||||
{
|
||||
my ($self, $file) = @_;
|
||||
my @paths;
|
||||
open (FILE, $file) or die 'Unable to open the list file: $file';
|
||||
|
||||
while (<FILE>) {
|
||||
|
||||
#sanitise the line
|
||||
s/^\s+|\s+$//g;
|
||||
s/^\/+|\/+$|\n+//g;
|
||||
s/\/+/\//g;
|
||||
#we don't want blank line or space
|
||||
next if /^$|\s+/;
|
||||
# we don't want some characters
|
||||
next if /`|'|"|;|&|\||#|\\|:|\*|<|>/;
|
||||
|
||||
push(@paths, $_);
|
||||
}
|
||||
close(FILE);
|
||||
|
||||
return @paths;
|
||||
}
|
||||
|
||||
|
||||
=head2 load_files_from_dir
|
||||
|
||||
Given a directory and an extension, return all lines
|
||||
from all files using load_file_list function.
|
||||
|
||||
=cut
|
||||
sub load_files_from_dir
|
||||
{
|
||||
my ($self, $dir, $extension ) = @_;
|
||||
my @ret;
|
||||
my @files = <$dir*.$extension>;
|
||||
foreach my $file (@files) {
|
||||
push(@ret,$self->load_file_list($file));
|
||||
}
|
||||
return @ret;
|
||||
}
|
||||
|
||||
=head2 includes
|
||||
|
||||
Takes a directory as argument.
|
||||
Returns a list of files from all .include files inside the given directory.
|
||||
All duplicates are removed.
|
||||
|
||||
=cut
|
||||
|
||||
sub includes
|
||||
{
|
||||
my ($self) = @_;
|
||||
return uniq($self->load_files_from_dir($BackupDir,'include'));
|
||||
}
|
||||
|
||||
|
||||
=head2 excludes
|
||||
|
||||
Takes a directory as argument.
|
||||
Returns a list of files from all .exclude files inside the given directory.
|
||||
All duplicates are removed.
|
||||
|
||||
=cut
|
||||
|
||||
sub excludes
|
||||
{
|
||||
my ($self) = @_;
|
||||
return uniq($self->load_files_from_dir($BackupDir,'exclude'));
|
||||
}
|
||||
|
||||
|
||||
=head2 merge_passwd
|
||||
|
||||
Merge password files. Takes a filename of a restored password
|
||||
file and an optional filename for the final merged password file,
|
||||
defaulting to /etc/passwd
|
||||
|
||||
=item *
|
||||
Save away the recently restored passwd file
|
||||
|
||||
=item *
|
||||
Put the pre-restore passwd file back in place
|
||||
|
||||
=item *
|
||||
Add back any users in the restored passwd file with home
|
||||
directories under directories which contain user or
|
||||
machine accounts
|
||||
|
||||
=item *
|
||||
Log any other missing users or UID/GID mismatches
|
||||
|
||||
=begin testing
|
||||
|
||||
my $installed = '10e-smith-backup/passwd-installed';
|
||||
my $restored = scratch_copy('10e-smith-backup/passwd-restored');
|
||||
|
||||
is($backup->merge_passwd($installed, $restored), 1, 'merge_passwd worked');
|
||||
|
||||
use Digest::MD5;
|
||||
open(FILE, '10e-smith-backup/passwd-merged') || die $!;
|
||||
my $srcmd5 = Digest::MD5->new->addfile(*FILE)->hexdigest;
|
||||
open(FILE, $restored) || die $1;
|
||||
my $destmd5 = Digest::MD5->new->addfile(*FILE)->hexdigest;
|
||||
close FILE;
|
||||
|
||||
is( $srcmd5, $destmd5, 'merge_passwd output looks good' );
|
||||
|
||||
=end testing
|
||||
|
||||
=cut
|
||||
|
||||
my @Scratch_Files = ();
|
||||
|
||||
END { unlink @Scratch_Files }
|
||||
|
||||
sub merge_passwd
|
||||
{
|
||||
my ($self, $pre_restored, $restored) = @_;
|
||||
|
||||
$restored ||= '/etc/passwd';
|
||||
|
||||
my $tmp = "${restored}.$$";
|
||||
push @Scratch_Files, $tmp;
|
||||
copy $restored, $tmp or warn "Couldn't copy $restored, $tmp\n";
|
||||
copy $pre_restored, $restored or warn "Couldn't copy $pre_restored, $restored\n";
|
||||
|
||||
my $merge_from = new Unix::PasswdFile($tmp, rmode => 'r' );
|
||||
|
||||
unless ($merge_from)
|
||||
{
|
||||
warn "merge_passwd: Couldn't open restored password object\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $merge_into = new Unix::PasswdFile($restored);
|
||||
|
||||
unless ($merge_into)
|
||||
{
|
||||
warn "merge_passwd: Couldn't open current password object\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
foreach my $user ($merge_from->users)
|
||||
{
|
||||
my @details = $merge_into->user($user);
|
||||
|
||||
if ( _homedir_ok($merge_from->home($user)) )
|
||||
{
|
||||
unless ( defined $details[0] )
|
||||
{
|
||||
$merge_into->user($user, $merge_from->user($user));
|
||||
warn "merge_passwd: Restoring user $user\n";
|
||||
}
|
||||
|
||||
next;
|
||||
}
|
||||
|
||||
unless ( defined $details[0] )
|
||||
{
|
||||
warn "merge_passwd: $user - Missing after restore\n";
|
||||
next;
|
||||
}
|
||||
|
||||
unless ( $merge_into->uid($user) eq $merge_from->uid($user) )
|
||||
{
|
||||
warn "merge_passwd: $user - UID changed during restore\n";
|
||||
next;
|
||||
}
|
||||
|
||||
unless ( $merge_into->gid($user) eq $merge_from->gid($user) )
|
||||
{
|
||||
warn "merge_passwd: $user - GID changed during restore\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
$merge_into->commit;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 merge_group
|
||||
|
||||
Merge group files. Takes a filename of a restored group
|
||||
file and an optional filename for the final merged group file,
|
||||
defaulting to /etc/group.
|
||||
|
||||
=item *
|
||||
Save away the recently restored group file
|
||||
|
||||
=item *
|
||||
Put the pre-restore group file back in place
|
||||
|
||||
=item *
|
||||
Add back any group in the restored group file for which
|
||||
there are corresponding users with valid home directories.
|
||||
These users are checked from the passwd file specified in the environment
|
||||
variable ESMITH_BACKUP_PASSWD_FILE, or /etc/passwd.
|
||||
|
||||
=item *
|
||||
Log any other missing groups or GID mismatches
|
||||
|
||||
=item *
|
||||
Adjust www, admin, shared groups
|
||||
|
||||
=begin testing
|
||||
|
||||
my $installed = '10e-smith-backup/group-installed';
|
||||
my $restored = scratch_copy('10e-smith-backup/group-restored');
|
||||
|
||||
$ENV{ESMITH_BACKUP_PASSWD_FILE} = '10e-smith-backup/passwd-merged';
|
||||
is($backup->merge_group($installed, $restored), 1, 'merge_group worked');
|
||||
|
||||
use Digest::MD5;
|
||||
open(FILE, '10e-smith-backup/group-merged') || die $!;
|
||||
my $srcmd5 = Digest::MD5->new->addfile(*FILE)->hexdigest;
|
||||
open(FILE, $restored) || die $1;
|
||||
my $destmd5 = Digest::MD5->new->addfile(*FILE)->hexdigest;
|
||||
close FILE;
|
||||
|
||||
is( $srcmd5, $destmd5, 'merge_group output looks good' );
|
||||
|
||||
=end testing
|
||||
|
||||
=cut
|
||||
|
||||
sub merge_group
|
||||
{
|
||||
my ($self, $pre_restored, $restored) = @_;
|
||||
|
||||
$restored ||= '/etc/group';
|
||||
|
||||
my $tmp = "${restored}.$$";
|
||||
push @Scratch_Files, $tmp;
|
||||
copy $restored, $tmp or warn "Couldn't copy $restored, $tmp\n";
|
||||
copy $pre_restored, $restored or warn "Couldn't copy $pre_restored, $restored\n";
|
||||
|
||||
my $merge_from = new Passwd::Unix(group => $tmp);
|
||||
|
||||
unless ($merge_from)
|
||||
{
|
||||
warn "merge_group: Couldn't open restored group object\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $merge_into = new Passwd::Unix(group => $restored);
|
||||
|
||||
unless ($merge_into)
|
||||
{
|
||||
warn "merge_group: Couldn't open current group object\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $passwd_file = $ENV{ESMITH_BACKUP_PASSWD_FILE} || '/etc/passwd';
|
||||
|
||||
my $passwd = new Unix::PasswdFile($passwd_file, rmode => 'r' );
|
||||
|
||||
unless ($passwd)
|
||||
{
|
||||
warn "merge_group: Couldn't open password object\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
foreach my $group ($merge_from->groups)
|
||||
{
|
||||
my @details = $merge_into->group($group);
|
||||
|
||||
if ( $passwd->user($group) and _homedir_ok($passwd->home($group)) )
|
||||
{
|
||||
unless ( defined $details[0] )
|
||||
{
|
||||
$merge_into->group($group, $merge_from->group($group));
|
||||
warn "merge_group: Restoring group $group\n";
|
||||
}
|
||||
|
||||
next;
|
||||
}
|
||||
|
||||
unless ( defined $details[0] )
|
||||
{
|
||||
warn "merge_group: $group - Missing after restore\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my ($merge_into_gid, undef) = $merge_into->group($group);
|
||||
my ($merge_from_gid, undef) = $merge_from->group($group);
|
||||
unless ($merge_into_gid eq $merge_from_gid)
|
||||
{
|
||||
warn "merge_group: $group - GID changed during restore\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $special_group ( qw(admin www shared) )
|
||||
{
|
||||
$merge_into->group($special_group, $merge_from->group($special_group));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 save_system_files
|
||||
|
||||
Save away system files which get cobbered by a restore
|
||||
|
||||
=cut
|
||||
|
||||
sub save_system_files
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
my $return = 1;
|
||||
|
||||
unless (chdir ESMITH_RESTORE_CACHE)
|
||||
{
|
||||
warn "Couldn't change to cache directory\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
foreach my $file ( $self->restore_list )
|
||||
{
|
||||
if ( -f "/$file" )
|
||||
{
|
||||
unless (copy "/$file", "./$file")
|
||||
{
|
||||
warn "Couldn't copy /$file to ./$file\n";
|
||||
$return = undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
=head2 merge_system_files
|
||||
|
||||
Merge restored system files with ones on the system
|
||||
|
||||
=cut
|
||||
|
||||
sub merge_system_files
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
unless (chdir ESMITH_RESTORE_CACHE)
|
||||
{
|
||||
warn "Couldn't change to cache directory\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
if ( -f "./etc/passwd" and -f "/etc/passwd" )
|
||||
{
|
||||
$self->merge_passwd( "./etc/passwd", "/etc/passwd" );
|
||||
}
|
||||
else
|
||||
{
|
||||
warn "Skipping password file merge\n";
|
||||
}
|
||||
|
||||
if ( -f "./etc/group" and -f "/etc/group" )
|
||||
{
|
||||
$self->merge_group( "./etc/group", "/etc/group" );
|
||||
}
|
||||
else
|
||||
{
|
||||
warn "Skipping group file merge\n";
|
||||
}
|
||||
|
||||
my $now = time();
|
||||
|
||||
foreach my $file ( $self->restore_list )
|
||||
{
|
||||
if ( -f "./$file" )
|
||||
{
|
||||
warn "Preserving $file as $file.$now\n";
|
||||
|
||||
rename "./$file", "./$file.$now"
|
||||
or warn "Couldn't rename ./$file, ./$file.$now\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
=head2 _homedir_ok
|
||||
|
||||
Returns true if the given directory is one we want to
|
||||
restore: /home/e-smith for user accounts or
|
||||
/noexistingpath for machine accounts
|
||||
|
||||
=cut
|
||||
|
||||
sub _homedir_ok
|
||||
{
|
||||
my $dir = shift or return;
|
||||
|
||||
return $dir =~ m:^/(home/e-smith|noexistingpath): ;
|
||||
}
|
||||
|
||||
=head2
|
||||
|
||||
set_lock - set lock before running backup
|
||||
see bug #9217
|
||||
|
||||
=cut
|
||||
|
||||
sub set_lock
|
||||
{
|
||||
return esmith::lockfile::LockFileOrReturn(ESMITH_BACKUP_LOCK_FILE);
|
||||
}
|
||||
|
||||
|
||||
=head2
|
||||
|
||||
remove_lock - remove lock after running backup
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_lock
|
||||
{
|
||||
esmith::lockfile::UnlockFile(shift);
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
SME Server Developers <bugs@e-smith.com>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
94
root/usr/share/perl5/vendor_perl/esmith/BackupHistoryDB.pm
Normal file
94
root/usr/share/perl5/vendor_perl/esmith/BackupHistoryDB.pm
Normal file
@@ -0,0 +1,94 @@
|
||||
#----------------------------------------------------------------------
|
||||
# Copyright 1999-2003 Mitel Networks Corporation
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the same terms as Perl itself.
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
package esmith::BackupHistoryDB;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use esmith::DB::db;
|
||||
our @ISA = qw( esmith::DB::db );
|
||||
|
||||
=head1 NAME
|
||||
|
||||
esmith::BackupHistoryDB - interface to esmith backup history database
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use esmith::BackupHistoryDB;
|
||||
my $c = esmith::BackupHistoryDB->open;
|
||||
|
||||
# everything else works just like esmith::DB::db
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module provides an abstracted interface to the esmith master
|
||||
configuration database.
|
||||
|
||||
Unless otherwise noted, esmith::BackupHistoryDB acts like esmith::DB::db.
|
||||
|
||||
=cut
|
||||
|
||||
=head2 open()
|
||||
|
||||
Like esmith::DB->open, but if given no $file it will try to open the
|
||||
file in the ESMITH_BACKUPHISTORY_DB environment variable or "backups" in
|
||||
the default database directory.
|
||||
|
||||
=begin testing
|
||||
|
||||
use_ok("esmith::BackupHistoryDB");
|
||||
|
||||
$C = esmith::BackupHistoryDB->open('10e-smith-backup/backuphistory.conf');
|
||||
isa_ok($C, 'esmith::BackupHistoryDB');
|
||||
|
||||
my $rec = $C->get('1053551285');
|
||||
isa_ok($rec, 'esmith::DB::Record');
|
||||
|
||||
is( $rec->prop('EndEpochTime'), 1053551299,
|
||||
"We can get stuff from the db");
|
||||
|
||||
=end testing
|
||||
|
||||
=cut
|
||||
|
||||
sub open {
|
||||
my($class, $file) = @_;
|
||||
$file = $file || $ENV{ESMITH_BACKUPHISTORY_DB} || "backups";
|
||||
return $class->SUPER::open($file) || $class->SUPER::create($file);
|
||||
}
|
||||
|
||||
=head2 open_ro()
|
||||
|
||||
Like esmith::DB->open_ro, but if given no $file it will try to open the
|
||||
file in the ESMITH_BACKUPHISTORY_DB environment variable or "backups" in
|
||||
the default database directory.
|
||||
|
||||
=begin testing
|
||||
|
||||
=end testing
|
||||
|
||||
=cut
|
||||
|
||||
sub open_ro {
|
||||
my($class, $file) = @_;
|
||||
$file = $file || $ENV{ESMITH_BACKUPHISTORY_DB} || "backups";
|
||||
return $class->SUPER::open_ro($file);
|
||||
}
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
SME Server Developers <bugs@e-smith.com>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<esmith::DB::db>
|
||||
|
||||
L<esmith::DB::Record>
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
510
root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm
Normal file
510
root/usr/share/perl5/vendor_perl/esmith/BlockDevices.pm
Normal file
@@ -0,0 +1,510 @@
|
||||
#----------------------------------------------------------------------
|
||||
# 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 => lsblk(),
|
||||
mount => findValidFS(),
|
||||
_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 (/.*\t(.*?)$/s){$fs {$1}=$1;}
|
||||
}
|
||||
@cmd = `ls -1 /lib/modules/\$(uname -r)/kernel/fs/*/*ko.*`;
|
||||
foreach (@cmd)
|
||||
{
|
||||
if (/.*\/(.*?)\.ko/s){$fs {$1}=$1;}
|
||||
}
|
||||
|
||||
# If ext4 driver is present, add ext2 and ext3
|
||||
if(exists($fs{ext4}))
|
||||
{
|
||||
$fs{'ext2'}='ext2';
|
||||
$fs{'ext3'}='ext3';
|
||||
}
|
||||
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(/usr/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;
|
@@ -0,0 +1,61 @@
|
||||
package esmith::console::backup_running;
|
||||
use strict;
|
||||
use warnings;
|
||||
use esmith::ConfigDB;
|
||||
use Locale::gettext;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub doit
|
||||
{
|
||||
my ($self, $console, $db) = @_;
|
||||
#-------------------------------------------------------------
|
||||
# check whether a backup in process and incomplete
|
||||
#-------------------------------------------------------------
|
||||
my $restore_db = esmith::ConfigDB->open_ro("/etc/e-smith/restore");
|
||||
return unless $restore_db;
|
||||
|
||||
my $restore_state = $restore_db->get_prop('restore', 'state') || 'idle';
|
||||
|
||||
return unless ($restore_state eq 'running');
|
||||
my ($rc, $choice) = $console->message_page
|
||||
(
|
||||
title => gettext("Inconsistent system state"),
|
||||
text =>
|
||||
gettext("********** Inconsistent system state detected ***********") .
|
||||
"\n\n" .
|
||||
gettext("The restoration of a system backup was running and incomplete at the time of the last reboot. The system should not be used in this state.") .
|
||||
"\n\n" .
|
||||
gettext("Consult the User Guide for further instructions."),
|
||||
);
|
||||
|
||||
($rc, $choice) = $console->yesno_page
|
||||
(
|
||||
title => gettext("System will be halted"),
|
||||
text =>
|
||||
gettext("The server will now be halted.") .
|
||||
"\n\n" .
|
||||
gettext("Consult the User Guide for recovery instructions.") .
|
||||
"\n\n" .
|
||||
gettext("Do you wish to halt the system right now?"),
|
||||
);
|
||||
|
||||
return unless ($rc == 0);
|
||||
|
||||
system("/usr/bin/tput", "clear");
|
||||
system("/sbin/e-smith/signal-event", "halt");
|
||||
|
||||
# A bit of a hack to avoid the console restarting before the
|
||||
# reboot takes effect.
|
||||
|
||||
sleep(600);
|
||||
}
|
||||
|
||||
1;
|
||||
|
@@ -0,0 +1,294 @@
|
||||
package esmith::console::perform_backup;
|
||||
use strict;
|
||||
use warnings;
|
||||
use esmith::ConfigDB;
|
||||
use esmith::console;
|
||||
use esmith::util;
|
||||
use Locale::gettext;
|
||||
use esmith::Backup;
|
||||
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{};
|
||||
|
||||
# lock file.. see bug 9127
|
||||
my $backup_lock;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
name => gettext("Perform backup to removable media"),
|
||||
order => 80,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub name
|
||||
{
|
||||
return $_[0]->{name};
|
||||
}
|
||||
|
||||
sub order
|
||||
{
|
||||
return $_[0]->{order};
|
||||
}
|
||||
|
||||
sub backup_size
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
}
|
||||
|
||||
# subs to set and remove lock on backup
|
||||
|
||||
sub SetLock
|
||||
{
|
||||
print "Setting backup lock file\n";
|
||||
$backup_lock = esmith::Backup::set_lock;
|
||||
return $backup_lock;
|
||||
}
|
||||
|
||||
sub RemoveLock
|
||||
{
|
||||
if (defined($backup_lock))
|
||||
{
|
||||
print "Removing backup lock file\n";
|
||||
esmith::Backup::remove_lock($backup_lock);
|
||||
$backup_lock = undef;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub make_backup_callback
|
||||
{
|
||||
my ($device, $CompressionLevel) = @_;
|
||||
return sub {
|
||||
my $fh = shift;
|
||||
my @backup_list = esmith::Backup->restore_list;
|
||||
my @backup_excludes = esmith::Backup->excludes;
|
||||
my $backup_size = backupSize (@backup_list);
|
||||
|
||||
# set lock.. if not, exit
|
||||
unless (SetLock()) {
|
||||
die "Error: failed to create lock file.. is a backup already running?";
|
||||
}
|
||||
|
||||
open(OLDSTDOUT, ">&STDOUT");
|
||||
unless (open(STDOUT, ">$device/smeserver.tgz"))
|
||||
{
|
||||
return gettext("Could not create backup file on device").": $!\n";
|
||||
}
|
||||
|
||||
open(OLDSTDERR, ">&STDERR");
|
||||
my $logger = open(STDERR, "|-");
|
||||
die "Can't fork: $!\n" unless defined $logger;
|
||||
|
||||
unless ($logger)
|
||||
{
|
||||
exec qw(/usr/bin/logger -p local1.info -t console_backup);
|
||||
}
|
||||
|
||||
my $status = 0;
|
||||
|
||||
my $gzip = open(GZIP, "|-");
|
||||
return "could not run gzip" unless defined $gzip;
|
||||
unless ($gzip)
|
||||
{
|
||||
close $fh;
|
||||
exec "gzip", $CompressionLevel;
|
||||
}
|
||||
|
||||
my $pv = open(PV, "|-");
|
||||
return "could not run pv" unless defined $pv;
|
||||
unless ($pv)
|
||||
{
|
||||
open(STDOUT, ">&GZIP");
|
||||
close GZIP;
|
||||
open(STDERR, ">&$fh");
|
||||
exec qw(pv -i 0.2 -n -s), $backup_size
|
||||
}
|
||||
|
||||
my $tar = fork;
|
||||
return "could not run tar" unless defined $tar;
|
||||
unless ($tar)
|
||||
{
|
||||
open(STDOUT, ">&PV");
|
||||
close PV;
|
||||
close GZIP;
|
||||
close $fh;
|
||||
chdir "/";
|
||||
#Create the archive
|
||||
my @directories = grep { -e $_ } @backup_list;
|
||||
my @exclude = map ("--exclude=$_",@backup_excludes);
|
||||
exec ("/bin/tar cf - @directories @exclude");
|
||||
}
|
||||
|
||||
waitpid($tar, 0);
|
||||
warn "status from tar was $?\n" if $?;
|
||||
unless (close PV)
|
||||
{
|
||||
$status |= $! ? $! : $?;
|
||||
warn "status from pv is $status\n" if $status;
|
||||
}
|
||||
unless (close GZIP)
|
||||
{
|
||||
$status |= $! ? $! : $?;
|
||||
warn "status from gzip is $status\n" if $status;
|
||||
}
|
||||
|
||||
open(STDOUT, ">&OLDSTDOUT");
|
||||
open(STDERR, ">&OLDSTDERR");
|
||||
close(OLDSTDERR);
|
||||
close(OLDSTDOUT);
|
||||
RemoveLock();
|
||||
return $status ? gettext("Backup failed. Look at the log files for more details.") : gettext("Backup successfully created.");
|
||||
};
|
||||
}
|
||||
|
||||
sub doit
|
||||
{
|
||||
my ($self, $console, $db) = @_;
|
||||
my @backup_list = esmith::Backup->restore_list;
|
||||
my @backup_excludes = esmith::Backup->excludes;
|
||||
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 $devices = esmith::BlockDevices->new ('mount' => $mountpoint, 'allowmount' => $allowMounted);
|
||||
|
||||
INITIATE_BACKUP:
|
||||
my ($rc, $choice) = $console->yesno_page
|
||||
(
|
||||
title => gettext("Create Backup to removable media"),
|
||||
defaultno => 1,
|
||||
text => gettext('Do you wish 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)
|
||||
{
|
||||
$devices->destroy;
|
||||
return;
|
||||
}
|
||||
### determine which filesystems are valid or not for backups
|
||||
# check expected backup size
|
||||
my $backup_size = backupSize (@backup_list);
|
||||
# validate each filesystem
|
||||
my ($valid, $invalid) = $devices->checkBackupDrives ($backup_size);
|
||||
my $text = $EMPTY;
|
||||
|
||||
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";
|
||||
}
|
||||
unless (${$valid}[0]) # Unless a device is found show error page
|
||||
{
|
||||
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
|
||||
|
||||
if (@backup_excludes) {
|
||||
my $backupexclude = join ("\n/", sort @backup_excludes);
|
||||
($rc, $choice) = $console->yesno_page
|
||||
(
|
||||
title => gettext("Some parts are excluded of your backup"),
|
||||
left => gettext("Next"),
|
||||
right => gettext("Cancel"),
|
||||
text => '/' . $backupexclude,
|
||||
);
|
||||
return unless $rc == 0;
|
||||
}
|
||||
|
||||
$console->infobox(
|
||||
title => gettext("Preparing for backup"),
|
||||
text => gettext("Please stand by while the system is prepared for 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');
|
||||
RemoveLock();
|
||||
$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 { "/$_" } @_;
|
||||
}
|
||||
while (<DU>)
|
||||
{
|
||||
next unless (/^(\d+)/);
|
||||
$size += $1;
|
||||
}
|
||||
close DU;
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
#use esmith::console;
|
||||
#esmith::console::perform_backup->new->doit(esmith::console->new,
|
||||
# esmith::ConfigDB->open);
|
||||
1;
|
@@ -0,0 +1,340 @@
|
||||
package esmith::console::perform_restore;
|
||||
use strict;
|
||||
use warnings;
|
||||
use esmith::ConfigDB;
|
||||
use esmith::console;
|
||||
use Locale::gettext;
|
||||
use Carp;
|
||||
use feature qw( say );
|
||||
use esmith::BlockDevices;
|
||||
use Taint::Util;
|
||||
use esmith::Backup;
|
||||
use Data::UUID;
|
||||
|
||||
my $EMPTY = q{};
|
||||
my $backup_lock;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
name => gettext("Restore from removable media"),
|
||||
order => installOrder(),
|
||||
bootstrap => 0,
|
||||
@_,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub name
|
||||
{
|
||||
return $_[0]->{name};
|
||||
}
|
||||
|
||||
sub order
|
||||
{
|
||||
return $_[0]->{order};
|
||||
}
|
||||
|
||||
sub SetLock
|
||||
{
|
||||
print "Setting backup lock file\n";
|
||||
$backup_lock = esmith::Backup::set_lock;
|
||||
return $backup_lock;
|
||||
}
|
||||
|
||||
sub RemoveLock
|
||||
{
|
||||
if (defined($backup_lock))
|
||||
{
|
||||
print "Removing backup lock file\n";
|
||||
esmith::Backup::remove_lock($backup_lock);
|
||||
$backup_lock = undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub new_restore_id
|
||||
{
|
||||
my $ug = Data::UUID->new;
|
||||
my $uid = $ug->create_str();
|
||||
return $uid;
|
||||
}
|
||||
|
||||
sub make_restore_callback
|
||||
{
|
||||
my ($mountpoint, $restoreid) = @_;
|
||||
return sub {
|
||||
my $fh = shift;
|
||||
|
||||
# Check no other backups or restores are in progress
|
||||
unless (SetLock()) {
|
||||
die "Error: failed to create lock file. Is a backup or restore already running?";
|
||||
}
|
||||
|
||||
open(OLDSTDOUT, ">&STDOUT");
|
||||
my $status = 0;
|
||||
chdir "/";
|
||||
open(OLDSTDERR, ">&STDERR");
|
||||
my $logger = open(STDERR, "|-");
|
||||
die "Can't fork: $!\n" unless defined $logger;
|
||||
|
||||
unless ($logger)
|
||||
{
|
||||
exec qw(/usr/bin/logger -p local1.info -t console_restore);
|
||||
}
|
||||
|
||||
my $tar = open(TAR, "|-");
|
||||
return "could not run tar" unless defined $tar;
|
||||
unless($tar)
|
||||
{
|
||||
exec "tar xf -";
|
||||
}
|
||||
|
||||
my $gunzip = open(GUNZIP, "|-");
|
||||
return "could not run gunzip" unless defined $gunzip;
|
||||
unless($gunzip)
|
||||
{
|
||||
open(STDOUT, ">&TAR");
|
||||
close TAR;
|
||||
exec "gunzip";
|
||||
}
|
||||
|
||||
my $pv = fork;
|
||||
return "could not run pv" unless defined $pv;
|
||||
unless ($pv)
|
||||
{
|
||||
open(STDOUT, ">&GUNZIP");
|
||||
open(STDERR, ">&$fh");
|
||||
close GUNZIP;
|
||||
close TAR;
|
||||
exec "pv -n $mountpoint/smeserver.tgz";
|
||||
}
|
||||
|
||||
waitpid($pv,0);
|
||||
warn "status from pv was $?\n" if $?;
|
||||
|
||||
unless(close GUNZIP)
|
||||
{
|
||||
$status |= $! ? $! : $?;
|
||||
warn "status from gunzip is $status\n" if $status;
|
||||
}
|
||||
|
||||
unless(close TAR)
|
||||
{
|
||||
$status |= $! ? $! : $?;
|
||||
warn "status from tar is $status\n" if $status;
|
||||
}
|
||||
|
||||
open(STDOUT, ">&OLDSTDOUT");
|
||||
open(STDERR, ">&OLDSTDERR");
|
||||
close(OLDSTDOUT);
|
||||
close(OLDSTDERR);
|
||||
|
||||
# Unlock and check for success or failure before returning
|
||||
RemoveLock();
|
||||
if ($status) {
|
||||
return gettext("Restore failed. Your system is likely in an inconsistent state - look at the log files for more details.");
|
||||
} else {
|
||||
system("touch", "/tmp/restore_$restoreid");
|
||||
return gettext("Restore successful.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub doit
|
||||
{
|
||||
my ($self, $console, $db) = @_;
|
||||
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
|
||||
|
||||
return if (($db->get_prop('bootstrap-console', 'Run') eq 'no') && $self->{bootstrap} ); # regular reboot
|
||||
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?')."\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;
|
||||
}
|
||||
### 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 ($backupcount) # Unless a valid backup is found show error page
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
# %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);
|
||||
$size = $found{1}{size};
|
||||
}
|
||||
else # Multiple backups found so display a choice
|
||||
{
|
||||
$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
|
||||
|
||||
# Prepare for restore
|
||||
$console->infobox(
|
||||
title => gettext("Preparing for restore"),
|
||||
text => gettext("Please stand by while the system is prepared for restore..."),
|
||||
);
|
||||
|
||||
system("/sbin/e-smith/signal-event", "pre-restore");
|
||||
|
||||
# Create restore ID to check success later
|
||||
my $restoreid = new_restore_id();
|
||||
|
||||
# Run restore
|
||||
$console->gauge(make_restore_callback($mountpoint, $restoreid), 'title' => gettext('Restoring from backup'));
|
||||
|
||||
# Restore complete, now clean-up. Only post-upgrade and reboot if the restore was successful.
|
||||
$devices->destroy;
|
||||
|
||||
if (-e "/tmp/restore_$restoreid") {
|
||||
system("/sbin/e-smith/signal-event", "post-upgrade");
|
||||
|
||||
$db->set_prop("bootstrap-console", "Run", "yes");
|
||||
$db->set_prop("bootstrap-console", "ForceSave", "yes");
|
||||
$db->set_prop("bootstrap-console", "Restore", "disabled");
|
||||
|
||||
unless ( $self->{bootstrap} )
|
||||
{
|
||||
($rc, $choice) = $console->yesno_page
|
||||
(
|
||||
title => gettext("Restore Complete"),
|
||||
text => gettext('You must restart your system to finish the restore.')."\n\n".
|
||||
gettext('Do you want to restart now?'),
|
||||
);
|
||||
return unless ($rc == 0);
|
||||
|
||||
system("/usr/bin/tput", "clear");
|
||||
system("/sbin/e-smith/signal-event", "reboot");
|
||||
|
||||
# A bit of a hack to avoid the console restarting before the
|
||||
# reboot takes effect.
|
||||
sleep(600);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# Determine if this server is a fresh install for restore from backup
|
||||
# Earlier the expression used was ($db->get_value('PasswordSet') eq 'yes')
|
||||
# To prevent a restore return a negative number
|
||||
# To allow a restore choose an appropiate sort order, eg 90
|
||||
sub installOrder
|
||||
{
|
||||
my $order = (`grep :x:5...: /etc/group`) ? -1 : 90;
|
||||
return $order;
|
||||
}
|
||||
|
||||
#use esmith::console;
|
||||
#esmith::console::perform_restore->new->doit(esmith::console->new,
|
||||
# esmith::ConfigDB->open);
|
||||
1;
|
||||
|
Reference in New Issue
Block a user