aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST4
-rwxr-xr-xbeam4
-rw-r--r--lib/App/Beam.pm166
-rw-r--r--lib/App/Beam/Backend.pm14
-rw-r--r--lib/App/Beam/Backend/Tar.pm81
-rw-r--r--lib/App/Beam/Backup.pm15
-rw-r--r--lib/App/Beam/History.pm76
-rw-r--r--lib/App/Beam/History/Entry.pm62
-rw-r--r--lib/App/Beam/History/Record.pm158
-rw-r--r--lib/App/Beam/List.pm43
-rw-r--r--lib/App/Beam/Restore.pm85
11 files changed, 536 insertions, 172 deletions
diff --git a/MANIFEST b/MANIFEST
index 48daf32..c8080a2 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,10 @@ lib/App/Beam/Backend.pm
lib/App/Beam/Backend/Tar.pm
lib/App/Beam/Command.pm
lib/App/Beam/List.pm
+lib/App/Beam/History.pm
+lib/App/Beam/Restore.pm
+lib/App/Beam/History/Entry.pm
+lib/App/Beam/History/Record.pm
t/TestConfig.pm
t/conf01.t
t/conf02.t
diff --git a/beam b/beam
index 3b55bc4..3154e71 100755
--- a/beam
+++ b/beam
@@ -76,6 +76,10 @@ my %ctab = (
use App::Beam::Backup;
return new App::Beam::Backup(@_);
},
+ restore => sub {
+ use App::Beam::Restore;
+ return new App::Beam::Restore(@_);
+ },
list => sub {
use App::Beam::List;
return new App::Beam::List(@_);
diff --git a/lib/App/Beam.pm b/lib/App/Beam.pm
index b7e004e..3bee8b9 100644
--- a/lib/App/Beam.pm
+++ b/lib/App/Beam.pm
@@ -5,16 +5,12 @@ use Carp;
use File::Basename;
use Sys::Syslog;
use Unix::Sysexits;
-use Fcntl qw(:flock SEEK_SET);
-use Storable qw(fd_retrieve nstore_fd);
use POSIX qw(strftime floor);
require App::Beam::Config;
our @ISA = qw(App::Beam::Config);
-our @EXPORT_OK = qw(RESULT_PENDING RESULT_SUCCESS RESULT_FAILURE);
-our %EXPORT_TAGS = (
- result => [ qw(RESULT_PENDING RESULT_SUCCESS RESULT_FAILURE) ]
-);
+
+use App::Beam::History;
my $default_config_file = '/etc/beam.conf';
@@ -255,130 +251,16 @@ sub abend {
exit($code);
}
-use constant {
- RESULT_PENDING => 0,
- RESULT_SUCCESS => 1,
- RESULT_FAILURE => 2
-};
-
-# Locks the statfile and retrieves data from it into {status}
-sub lock {
- my $self = shift;
- my $file = $self->get('core.statfile');
- unless (open($self->{statfd}, '+>>', $file)) {
- $self->abend(EX_CANTCREAT, "can't open file $file: $!");
- }
- unless (flock($self->{statfd}, LOCK_EX | LOCK_NB)) {
- $self->abend(EX_TEMPFAIL, "can't lock file $file: $!");
- }
- seek($self->{statfd}, 0, SEEK_SET);
- if ((stat($self->{statfd}))[7] == 0) {
- $self->{status} = [];
- } else {
- $self->{status} = fd_retrieve($self->{statfd})
- or $self->abend(EX_UNAVAILABLE, "can't retrieve status from $file: $!");
- if (ref($self->{status}) ne 'ARRAY') {
- $self->abend(EX_DATAERR, "$file: malformed status file");
- }
- }
- my ($cycle, $round, $level) = $self->compute_triplet(1);
- unshift @{$self->{status}}, { timestamp => time(),
- result => RESULT_PENDING,
- cycle => $cycle,
- round => $round,
- level => $level };
-}
-
-# Saves the current status and unlocks the status file.
-sub unlock {
- my ($self) = @_;
- croak "unlock without lock" unless defined $self->{statfd};
- $self->debug(1, "saving state: ".$self->status('result'));
- if (!$self->{dry_run} && $self->status('result') != RESULT_PENDING) {
- seek($self->{statfd}, 0, SEEK_SET);
- truncate($self->{statfd}, 0);
- nstore_fd($self->{status}, $self->{statfd});
- }
- flock($self->{statfd}, LOCK_UN);
- close($self->{statfd});
-}
-
-sub compute_triplet {
- my ($self, $off, $base) = @_;
-
- $base = 0 unless defined $base;
- if ($#{$self->{status}} == -1) {
- return (0, 0, 0);
- }
- croak "invalid base" unless $base <= $#{$self->{status}};
-
- my ($cycle, $round, $level) =
- (${$self->{status}}[$base]{cycle},
- ${$self->{status}}[$base]{round},
- ${$self->{status}}[$base]{level});
-
- my ($maxround, $maxlevel) =
- ($self->get('schedule', 'rounds'),
- $self->get('schedule', 'levels'));
- if ($off == 1) {
- $level++;
- if ($level > $maxlevel) {
- $round++;
- $level = 0;
- if ($round > $maxround) {
- $cycle++;
- $round = 0;
- }
- }
- } elsif ($off == -1) {
- $level--;
- if ($level < 0) {
- $level += $maxlevel + 1;
- $round--;
- }
- if ($round < 0) {
- $round += $maxround + 1;
- $cycle--;
- if ($cycle == 0) {
- $round = $level = 0;
- last;
- }
- }
- } elsif ($off) {
- croak "invalid offset";
- }
- return ($cycle, $round, $level);
-}
-
-sub status {
- my ($self,$item,$base) = @_;
- $base = 0 unless defined $base;
- return undef unless $base <= $#{$self->{status}};
- return $self->{status}[$base]{$item};
-}
-
-sub result {
- my ($self) = @_;
- return $self->{status}[0]{result};
-}
-
-sub set_result {
- my ($self, $item, $result) = @_;
- $self->{status}[0]{detail}{$item} = $result;
- $self->{status}[0]{result} = $result
- unless $self->{status}[0]{result} == RESULT_FAILURE;
-}
-
sub format_name {
- my ($self, $name, $base) = @_;
- $base = 0 unless defined $base;
- return undef unless $base <= $#{$self->{status}};
+ my ($self, $name, $idx) = @_;
+ my $rec = $self->{history}->top($idx);
+ return undef unless defined $rec;
$name .= '-'
- . $self->status('cycle', $base)
+ . $rec->cycle
. '-'
- . $self->status('round', $base)
+ . $rec->round
. '-'
- . $self->status('level', $base);
+ . $rec->level;
return $name;
}
@@ -458,11 +340,27 @@ sub begin {
}
$self->logger('info', 'startup');
- $self->load_backends;
+ $self->load_backends;
+
+ eval {
+ my %args;
+ $args{dry_run} = $self->{dry_run};
+ $self->{history} =
+ new App::Beam::History($self->get('core.statfile'),
+ $self->get('schedule', 'rounds'),
+ $self->get('schedule', 'levels'),
+ %args);
+ };
+ if ($@) {
+ $self->logger('crit', $@);
+ exit(EX_CANTCREAT);
+ }
}
sub end {
my $self = shift;
+ $self->{history}->top->finish;
+ $self->{history}->save;
$self->logger('info', 'shutdown');
if ($self->get('logger.channel') eq 'syslog') {
closelog();
@@ -474,4 +372,18 @@ sub run {
$self->abend(EX_SOFTWARE, "unsupported command $_[0]");
}
+sub check_items {
+ my $self = shift;
+ my @items = split /\s+/, $self->get('core.items');
+ if (@_) {
+ push @items, $self->names_of('item');
+ foreach my $item (@_) {
+ $self->abend(EX_USAGE, "$item: no such item defined")
+ unless grep { $item eq $_ } @items;
+ }
+ @items = @_;
+ }
+ return @items;
+}
+
1;
diff --git a/lib/App/Beam/Backend.pm b/lib/App/Beam/Backend.pm
index c0b8ee3..41036e6 100644
--- a/lib/App/Beam/Backend.pm
+++ b/lib/App/Beam/Backend.pm
@@ -5,6 +5,7 @@ use Carp;
require Exporter;
our @ISA = qw(Exporter);
+use App::Beam;
use App::Beam::Command qw(:channels);
sub new {
@@ -48,9 +49,9 @@ sub isset {
return $self->{beam}->isset(@_);
}
-sub status {
- my $self = shift;
- return $self->{beam}->status(@_);
+sub record {
+ my ($self,$idx) = @_;
+ return $self->{beam}{history}->top($idx);
}
sub logcommand {
@@ -92,4 +93,11 @@ sub backup {
return => '-locus'}));
}
+sub restore {
+ my ($self, $index, $item) = @_;
+ $self->error("restore method not implemented",
+ locus => $self->get({variable => [ 'item', $item ],
+ return => '-locus'}));
+}
+
1;
diff --git a/lib/App/Beam/Backend/Tar.pm b/lib/App/Beam/Backend/Tar.pm
index 9035427..bbb4730 100644
--- a/lib/App/Beam/Backend/Tar.pm
+++ b/lib/App/Beam/Backend/Tar.pm
@@ -8,8 +8,9 @@ use Data::Dumper;
our @ISA = qw(App::Beam::Backend);
use POSIX qw(strftime);
-use App::Beam qw(:result);
+use App::Beam;
use App::Beam::Command qw(:channels);
+use App::Beam::History::Entry qw(:state);
sub ck_dir {
my ($v) = @_;
@@ -102,7 +103,7 @@ sub snapshot_name {
sub mksnapshot {
my ($self, $item) = @_;
my $snapshot = $self->snapshot_name($item);
- if ($self->status('level') != 0) {
+ if ($self->record->level != 0) {
my $prev = $self->snapshot_name($item, 1);
if ($prev) {
$self->debug(1, "cp $prev $snapshot");
@@ -151,14 +152,82 @@ sub backup {
}
$self->debug(1, "running ".$cmd->command_line);
+ my $ret = STATE_SUCCESS;
unless ($self->dry_run) {
$cmd->run;
$self->logcommand($cmd);
- my $ret = $cmd->exit_code;
- $self->{beam}->set_result($item,
- ($ret == 0 || $ret == 2)
- ? RESULT_SUCCESS : RESULT_FAILURE);
+ my $rc = $cmd->exit_code;
+ $ret = STATE_FAILURE unless ($rc == 0 || $rc == 2);
}
+ return $ret;
}
+
+sub find_full_backup {
+ my ($self, $index, $item) = @_;
+
+ my $rec = $self->record($index);
+ my $cycle = $rec->cycle;
+ my $i;
+ for ($i = $index; $rec = $self->record($i); $i++) {
+ unless ($rec->cycle == $cycle) {
+ $self->logger('err', "can't find full backup of $item starting from $index");
+ last;
+ }
+ unless (defined($rec->state($item))) {
+ $self->logger('err', "no item $item in backup $i");
+ last;
+ }
+ return $i if $rec->level == 0;
+ }
+ return undef;
+}
+
+sub restore_from {
+ my ($self, $index, $item) = @_;
+
+ print "restore $item from $index\n";
+ my $basename = $self->{beam}->format_name($item, $index);
+ croak "undefined basename" unless defined $basename;
+ my $archive = $self->get('core', 'archivedir')
+ . '/'
+ . $basename
+ . '.'
+ . $self->get('backend', 'tar', 'suffix');
+
+ my $cmd = new App::Beam::Command($self->get('backend', 'tar', 'binary'));
+ $cmd->set_logger(CHAN_STDERR, sub { $self->logger('err', $_) });
+ if ($self->isset('backend', 'tar', 'options')) {
+ $cmd->add($self->get('backend', 'tar', 'options'));
+ }
+ if ($self->isset('item', $item, 'options')) {
+ $cmd->add($self->get('item', $item, 'options'));
+ }
+ $cmd->add('-x');
+ $cmd->add('-f', $archive);
+ $cmd->add('-C', $self->get('item', $item, 'directory'));
+ $cmd->add('--listed', '/dev/null');
+
+ $self->debug(1, "running ".$cmd->command_line);
+ my $ret = STATE_SUCCESS;
+ unless ($self->dry_run) {
+ $cmd->run;
+ $self->logcommand($cmd);
+ my $rc = $cmd->exit_code;
+ $ret = STATE_FAILURE unless ($rc == 0 || $rc == 2);
+ }
+ return $ret;
+}
+
+sub restore {
+ my ($self, $index, $item) = @_;
+ my $i = $self->find_full_backup($index, $item);
+ return unless defined $i;
+
+ while ($i >= $index) {
+ $self->restore_from($i, $item);
+ $i--;
+ }
+}
+
1;
diff --git a/lib/App/Beam/Backup.pm b/lib/App/Beam/Backup.pm
index ed8953b..c2da254 100644
--- a/lib/App/Beam/Backup.pm
+++ b/lib/App/Beam/Backup.pm
@@ -42,22 +42,13 @@ sub run {
my $self = shift;
#GetOptionsFromArray(\@_, ...);
- my @items = split /\s+/, $self->get('core.items');
- if (@_) {
- push @items, $self->names_of('item');
- foreach my $item (@_) {
- $self->abend(EX_USAGE, "$item: no such item defined")
- unless grep { $item eq $_ } @items;
- }
- @items = @_;
- }
+ my @items = $self->check_items(@_);
- $self->lock();
foreach my $item (@items) {
my $backend = $self->{backend}{$self->get("item.$item.backend")};
- $backend->backup($item);
+ $self->{history}->top->begin_entry($item);
+ $self->{history}->top->finish_entry($item, $backend->backup($item));
}
- $self->unlock();
}
1;
diff --git a/lib/App/Beam/History.pm b/lib/App/Beam/History.pm
new file mode 100644
index 0000000..0421c3e
--- /dev/null
+++ b/lib/App/Beam/History.pm
@@ -0,0 +1,76 @@
+package App::Beam::History;
+
+use strict;
+use Carp;
+use App::Beam::History::Record;
+use App::Beam::History::Entry qw(:state);
+
+require Exporter;
+our @ISA = qw(Exporter);
+
+use Fcntl qw(:flock SEEK_SET);
+use Storable qw(fd_retrieve nstore_fd);
+
+sub new {
+ my $class = shift;
+ my $filename = shift;
+ my $rounds = shift;
+ my $levels = shift;
+
+ my $self = bless { filename => $filename }, $class;
+
+ my $v;
+ local %_ = @_;
+ if (defined($v = delete($_{dry_run}))) {
+ $self->{dry_run} = $v;
+ }
+
+ if (keys(%_)) {
+ croak "unknown or unhandled parameters: ".join(',', keys(%_));
+ }
+
+ unless (open($self->{filehandle}, '+>>', $filename)) {
+ croak "can't open file $filename: $!";
+ }
+ unless (flock($self->{filehandle}, LOCK_EX | LOCK_NB)) {
+ croak "can't lock file $filename: $!";
+ }
+ seek($self->{filehandle}, 0, SEEK_SET);
+ if ((stat($self->{filehandle}))[7] == 0) {
+ $self->{history} = [];
+ unshift @{$self->{history}}, new App::Beam::History::Record;
+ } else {
+ $self->{history} = fd_retrieve($self->{filehandle})
+ or croak "can't retrieve status from $self->{filehandle}: $!";
+ if (ref($self->{history}) ne 'ARRAY') {
+ croak "$self->{filename}: malformed status file";
+ }
+ unshift @{$self->{history}},
+ new App::Beam::History::Record(prev => $self->{history}[0],
+ rounds => $rounds,
+ levels => $levels);
+ }
+ return $self;
+}
+
+sub records {
+ my $self = shift;
+ return @{$self->{history}};
+}
+
+sub top {
+ my ($self, $idx) = @_;
+ return $self->{history}[$idx];
+}
+
+sub save {
+ my $self = shift;
+ if (!$self->{dry_run} && $self->top->state != STATE_PENDING) {
+ seek($self->{filehandle}, 0, SEEK_SET);
+ truncate($self->{filehandle}, 0);
+ nstore_fd($self->{history}, $self->{filehandle});
+ }
+ flock($self->{filehandle}, LOCK_UN);
+ close($self->{filehandle});
+}
+
diff --git a/lib/App/Beam/History/Entry.pm b/lib/App/Beam/History/Entry.pm
new file mode 100644
index 0000000..8443f3f
--- /dev/null
+++ b/lib/App/Beam/History/Entry.pm
@@ -0,0 +1,62 @@
+package App::Beam::History::Entry;
+
+use strict;
+use Carp;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(STATE_PENDING STATE_SUCCESS STATE_FAILURE);
+our %EXPORT_TAGS = (
+ state => [ qw(STATE_PENDING STATE_SUCCESS STATE_FAILURE) ]
+);
+
+use constant {
+ STATE_PENDING => 0,
+ STATE_SUCCESS => 1,
+ STATE_FAILURE => 2
+};
+
+my @state_str = (
+ 'PENDING',
+ 'SUCCESS',
+ 'FAILURE'
+);
+
+sub new {
+ my $class = shift;
+ return bless { start_time => time(),
+ end_time => time(),
+ state => STATE_PENDING }, $class;
+}
+
+sub state {
+ my ($self, $value) = @_;
+ if (defined($value)) {
+ croak "can't modify finished entry"
+ unless $self->{state} == STATE_PENDING;
+ $self->{state} = $value;
+ $self->{end_time} = time();
+ }
+ return $self->{state};
+}
+
+sub state_name {
+ my ($self) = @_;
+ return $state_str[$self->state];
+}
+
+sub start_time {
+ my ($self) = @_;
+ return $self->{start_time};
+}
+
+sub end_time {
+ my ($self) = @_;
+ return $self->{end_time};
+}
+
+1;
+
+
+
+
diff --git a/lib/App/Beam/History/Record.pm b/lib/App/Beam/History/Record.pm
new file mode 100644
index 0000000..6ec2bd3
--- /dev/null
+++ b/lib/App/Beam/History/Record.pm
@@ -0,0 +1,158 @@
+package App::Beam::History::Record;
+
+use strict;
+use Carp;
+use App::Beam::History::Entry qw(:state);
+
+our @ISA = qw(App::Beam::History::Entry);
+
+sub new {
+ my $class = shift;
+ my $self = $class->SUPER::new;
+ $self->{detail} = {};
+
+ local %_ = @_;
+ my $v;
+
+ if (defined($v = delete($_{prev}))) {
+ my ($maxround, $maxlevel);
+ unless (defined($maxround = delete($_{rounds}))) {
+ croak "'prev' requires parameter 'rounds', which is not given";
+ }
+ unless (defined($maxlevel = delete($_{levels}))) {
+ croak "'prev' requires parameter 'levels', which is not given";
+ }
+# print "PREV ".$v->level."\n";
+ ($self->{level}, $self->{round}, $self->{cycle}) =
+ ($v->level, $v->round, $v->cycle);
+ $self->{level}++;
+ if ($self->{level} > $maxlevel) {
+ $self->{level} = 0;
+ $self->{round}++;
+ if ($self->{round} > $maxround) {
+ $self->{round} = 0;
+ $self->{cycle}++;
+ }
+ }
+ } else {
+ if (defined($v = delete($_{cycle}))) {
+ $self->{cycle} = $v;
+ } else {
+ $self->{cycle} = 0;
+ }
+
+ if (defined($v = delete($_{round}))) {
+ $self->{round} = $v;
+ } else {
+ $self->{round} = 0;
+ }
+
+ if (defined($v = delete($_{level}))) {
+ $self->{level} = $v;
+ } else {
+ $self->{level} = 0;
+ }
+ }
+
+ if (keys(%_)) {
+ croak "unknown or unhandled parameters: ".join(',', keys(%_));
+ }
+
+ return $self;
+}
+
+sub cycle {
+ my ($self) = @_;
+ return $self->{cycle};
+}
+
+sub round {
+ my ($self) = @_;
+ return $self->{round};
+}
+
+sub level {
+ my ($self) = @_;
+ return $self->{level};
+}
+
+sub state {
+ my ($self, $item) = @_;
+ if (defined($item)) {
+ return undef unless exists $self->{detail}{$item};
+ return $self->{detail}{$item}->state;
+ }
+ return $self->{state};
+}
+
+sub start_time {
+ my ($self, $item) = @_;
+ if (defined($item)) {
+ return undef unless exists $self->{detail}{$item};
+ return $self->{detail}{$item}->start_time;
+ }
+ return $self->SUPER::start_time;
+}
+
+sub end_time {
+ my ($self, $item) = @_;
+ if (defined($item)) {
+ return undef unless exists $self->{detail}{$item};
+ return $self->{detail}{$item}->end_time;
+ }
+ return $self->SUPER::end_time;
+}
+
+sub begin_entry {
+ my ($self, $name) = @_;
+ croak "can't add entry to a finished record"
+ unless $self->{state} == STATE_PENDING;
+ my $entry = new App::Beam::History::Entry;
+ $self->{detail}{$name} = $entry;
+ if ($entry->start_time > $self->{end_time}) {
+ $self->{end_time} = $entry->start_time;
+ }
+}
+
+sub entries {
+ my ($self) = @_;
+ return %{$self->{detail}};
+}
+
+sub entry_names {
+ my ($self) = @_;
+ return keys %{$self->{detail}};
+}
+
+sub finish_entry {
+ my ($self, $name, $state) = @_;
+ croak "can't modify item in a finished record"
+ unless $self->{state} == STATE_PENDING;
+ croak "no such entry"
+ unless exists $self->{detail}{$name};
+ my $entry = $self->{detail}{$name};
+ $entry->state($state);
+ if ($entry->end_time > $self->{end_time}) {
+ $self->{end_time} = $entry->end_time;
+ }
+}
+
+sub finish {
+ my ($self) = @_;
+ croak "can't finish a finished record"
+ unless $self->{state} == STATE_PENDING;
+ my @x = map {$_->state} values %{$self->{detail}};
+ if (@x) {
+ $self->{state} = (grep { $_ != STATE_SUCCESS } @x)
+ ? STATE_FAILURE
+ : STATE_SUCCESS;
+ }
+ return $self->{state};
+}
+
+1;
+
+
+
+
+
diff --git a/lib/App/Beam/List.pm b/lib/App/Beam/List.pm
index b786e3f..018f7e3 100644
--- a/lib/App/Beam/List.pm
+++ b/lib/App/Beam/List.pm
@@ -5,17 +5,13 @@ use Carp;
use POSIX qw(strftime);
use Unix::Sysexits;
use Getopt::Long qw(GetOptionsFromArray);
-use Pod::Usage;
-use Pod::Man;
-use App::Beam qw(:result);
+use App::Beam;
our @ISA = qw(App::Beam);
-my %result_string = (
- RESULT_PENDING() => 'PENDING',
- RESULT_SUCCESS() => 'SUCCESS',
- RESULT_FAILURE() => 'FAILURE'
-);
+use App::Beam::History;
+use App::Beam::History::Record;
+use App::Beam::History::Entry;
=head1 NAME
@@ -71,36 +67,35 @@ sub run {
my $verbose;
GetOptionsFromArray(\@_,
- "verbose|v" => \$verbose);
+ "verbose|v" => \$verbose)
+ or exit(EX_USAGE);
$self->abend(EX_USAGE, "bad number of arguments") if @_;
- $self->lock();
- my @stat = @{$self->{status}};
- shift @stat;
+ my @records = $self->{history}->records;
+ shift @records;
my $i = 0;
- foreach my $ent (@stat) {
+ foreach my $rec (@records) {
++$i;
printf("%3d ", $i);
- print strftime "%c ", localtime $ent->{timestamp};
- printf("% 3d% 3d% 3d %s", $ent->{cycle}, $ent->{round}, $ent->{level},
- $result_string{$ent->{result}});
+ print strftime "%c ", localtime $rec->start_time;
+ print strftime "- %c ", localtime $rec->end_time;
+ printf("% 3d% 3d% 3d %s", $rec->cycle, $rec->round, $rec->level,
+ $rec->state_name);
print "\n";
if ($verbose) {
- my %detail;
- @detail{keys %{$ent->{detail}}} = values %{$ent->{detail}};
+ my %ent = $rec->entries;
foreach my $item (split /\s+/, $self->get('core.items')) {
- if (exists($detail{$item})) {
- printf("# %-24s %s\n", $item, $result_string{$detail{$item}});
- delete $detail{$item};
+ if (exists($ent{$item})) {
+ printf("# %-24s %s\n", $item, $ent{$item}->state_name);
+ delete $ent{$item};
}
}
- foreach my $item (sort keys %detail) {
- printf("# %-24s %s\n", $item, $result_string{$detail{$item}});
+ foreach my $item (sort keys %ent) {
+ printf("# %-24s %s\n", $item, $ent{$item}->state_name);
}
}
}
- $self->unlock();
}
1;
diff --git a/lib/App/Beam/Restore.pm b/lib/App/Beam/Restore.pm
new file mode 100644
index 0000000..7e3129a
--- /dev/null
+++ b/lib/App/Beam/Restore.pm
@@ -0,0 +1,85 @@
+package App::Beam::Restore;
+
+use strict;
+use Carp;
+
+require App::Beam;
+our @ISA = qw(App::Beam);
+
+use Unix::Sysexits;
+use App::Beam;
+use App::Beam::History::Entry qw(:state);
+use Getopt::Long qw(GetOptionsFromArray);
+
+=head1 NAME
+
+beam restore - restore from a backup
+
+=head1 SYNOPSIS
+
+B<beam> [I<OPTIONS>] B<restore> [I<ITEM>...]
+
+=head1 DESCRIPTION
+
+To be written...
+
+=cut
+
+sub find_index {
+ my ($self, $cycle, $round, $level) = @_;
+ my @comp;
+ push @comp, sub { $_->cycle == $cycle } if defined $cycle;
+ push @comp, sub { $_->round == $round } if defined $round;
+ push @comp, sub { $_->level == $level } if defined $level;
+
+ my $index = 1;
+ return $index unless @comp;
+ OUTER:
+ for (; my $rec = $self->{history}->top($index); $index++) {
+ local $_ = $rec;
+ foreach my $c (@comp) {
+ next OUTER unless &{$c};
+ }
+ return $index;
+ }
+ return undef;
+}
+
+sub run {
+ my $self = shift;
+
+ my ($index, $cycle, $round, $level);
+ my $force;
+ GetOptionsFromArray(\@_,
+ "number|N=n" => \$index,
+ "level|l=n" => \$level,
+ "round|r=n" => \$round,
+ "cycle|c=n" => \$cycle,
+ "force|f" => \$force
+ ) or exit(EX_USAGE);
+ my @items = $self->check_items(@_);
+
+ if (defined($index)) {
+ $self->abend(EX_USAGE,
+ "--index can't be used together with --cycle, --round, or --level")
+ if defined($cycle) || defined($round) || defined($level);
+ } else {
+ $index = $self->find_index($cycle, $round, $level);
+ $self->abend(EX_USAGE, "matching backup not found")
+ unless defined $index;
+ }
+
+ $self->debug(1, "restoring from backup $index");
+ $self->abend(EX_UNAVAILABLE, "backup $index was not successful")
+ unless $self->{history}->top($index)->state == STATE_SUCCESS;
+
+ foreach my $item (@items) {
+ my $backend = $self->{backend}{$self->get("item.$item.backend")};
+ $backend->restore($index, $item);
+ }
+}
+
+1;
+
+
+

Return to:

Send suggestions and report system problems to the System administrator.