summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changes29
-rw-r--r--META.json15
-rw-r--r--README.md6
-rw-r--r--cpanfile3
-rw-r--r--lib/App/BorgRestore.pm13
-rw-r--r--lib/App/BorgRestore/Borg.pm12
-rw-r--r--lib/App/BorgRestore/DB.pm130
-rw-r--r--lib/App/BorgRestore/Helper.pm7
-rw-r--r--lib/App/BorgRestore/PathTimeTable/DB.pm42
-rw-r--r--lib/App/BorgRestore/Settings.pm6
-rwxr-xr-xscript/borg-restore.pl2
-rw-r--r--t/handle_added_archives_with_db.t28
-rw-r--r--t/helper/untaint.t19
13 files changed, 235 insertions, 77 deletions
diff --git a/Changes b/Changes
index 807706c..092aa83 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,35 @@
Revision history for Perl extension App-BorgRestore
{{$NEXT}}
+
+3.4.5 2023-09-03T11:58:14Z
+ - Fix deprecation warning with borg 1.2.x
+
+3.4.4 2020-10-14T12:37:04Z
+ - Require DBD::SQLite 1.60 or newer to fix issues with schema migration 2
+ on old systems.
+ - Reduce database size for databases that contained data from before
+ migrations 3 and 4.
+
+3.4.3 2020-09-27T13:53:54Z
+ - Add database migration for change from 3.4.2
+
+3.4.2 2020-09-27T13:50:47Z
+ - Fix missing cache data for top level files that exist with and without an
+ extension. For example a '/lib' symlink and '/lib64'. '/lib' would be
+ missing from the database.
+
+3.4.1 2020-09-27T12:57:01Z
+ - Fix missing cache data for files that exist with and without an
+ extension. For example '/home/*/.ssh/id_rsa' would be missing from the
+ database and only the accompanying `id_rsa.pub` file would be contained
+ in the database.
+
+3.4.0 2019-09-28T13:28:49Z
+ - Remove archive name untaint restrictions (remove untaint_archive_name
+ function)
+
+3.3.0 2019-02-07T16:18:41Z
- Support borg list's --prefix option via $borg_prefix setting
- Properly handle cases where the DB is empty after removal of archive
information
diff --git a/META.json b/META.json
index baab14c..56fdf2c 100644
--- a/META.json
+++ b/META.json
@@ -1,10 +1,10 @@
{
"abstract" : "Restore paths from borg backups",
"author" : [
- "-2018 Florian Pritz <bluewind@xinu.at>"
+ "Florian Pritz <bluewind@xinu.at>"
],
"dynamic_config" : 0,
- "generated_by" : "Minilla/v3.1.2, CPAN::Meta::Converter version 2.150010",
+ "generated_by" : "Minilla/v3.1.22, CPAN::Meta::Converter version 2.150010",
"license" : [
"gpl_3"
],
@@ -36,14 +36,15 @@
"requires" : {
"Test::CPAN::Meta" : "0",
"Test::MinimumVersion::Fast" : "0.04",
- "Test::PAUSE::Permissions" : "0.04",
+ "Test::PAUSE::Permissions" : "0.07",
"Test::Pod" : "1.41",
"Test::Spellunker" : "v0.2.7"
}
},
"runtime" : {
"requires" : {
- "DBD::SQLite" : "0",
+ "Carp::Assert" : "0",
+ "DBD::SQLite" : "1.60",
"DBI" : "0",
"Date::Parse" : "0",
"File::pushd" : "0",
@@ -89,11 +90,11 @@
"homepage" : "https://github.com/Bluewind/App-BorgRestore",
"repository" : {
"type" : "git",
- "url" : "git://github.com/Bluewind/App-BorgRestore.git",
+ "url" : "https://github.com/Bluewind/App-BorgRestore.git",
"web" : "https://github.com/Bluewind/App-BorgRestore"
}
},
- "version" : "3.2.1",
- "x_serialization_backend" : "JSON::PP version 2.97001",
+ "version" : "3.4.5",
+ "x_serialization_backend" : "JSON::PP version 4.07",
"x_static_install" : 0
}
diff --git a/README.md b/README.md
index 13f1380..060d3a6 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ passed and restore it without further user interaction.
**borg-restore.pl --update-cache** has to be executed regularly, ideally after
creating or removing backups.
-[App::BorgRestore](https://metacpan.org/pod/App::BorgRestore) provides the base features used to implement this script.
+[App::BorgRestore](https://metacpan.org/pod/App%3A%3ABorgRestore) provides the base features used to implement this script.
It can be used to build your own restoration script.
# OPTIONS
@@ -125,11 +125,11 @@ It can be used to build your own restoration script.
# CONFIGURATION
-For configuration options please see [App::BorgRestore::Settings](https://metacpan.org/pod/App::BorgRestore::Settings).
+For configuration options please see [App::BorgRestore::Settings](https://metacpan.org/pod/App%3A%3ABorgRestore%3A%3ASettings).
# LICENSE
-Copyright (C) 2016-2018 Florian Pritz <bluewind@xinu.at>
+Copyright (C) 2016-2023 Florian Pritz <bluewind@xinu.at>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/cpanfile b/cpanfile
index d5db169..aaccf5c 100644
--- a/cpanfile
+++ b/cpanfile
@@ -1,4 +1,5 @@
-requires 'DBD::SQLite';
+requires 'Carp::Assert';
+requires 'DBD::SQLite', '1.60';
requires 'DBI';
requires 'Date::Parse';
requires 'File::pushd';
diff --git a/lib/App/BorgRestore.pm b/lib/App/BorgRestore.pm
index 1e923c5..971e709 100644
--- a/lib/App/BorgRestore.pm
+++ b/lib/App/BorgRestore.pm
@@ -2,11 +2,11 @@ package App::BorgRestore;
use v5.14;
use strictures 2;
-our $VERSION = "3.2.1";
+our $VERSION = "3.4.5";
use App::BorgRestore::Borg;
use App::BorgRestore::DB;
-use App::BorgRestore::Helper;
+use App::BorgRestore::Helper qw(untaint);
use App::BorgRestore::PathTimeTable::DB;
use App::BorgRestore::PathTimeTable::Memory;
use App::BorgRestore::Settings;
@@ -327,9 +327,8 @@ process during method execution since this is required by C<`borg extract`>.
=cut
method restore($path, $archive, $destination) {
- $destination = App::BorgRestore::Helper::untaint($destination, qr(.*));
- $path = App::BorgRestore::Helper::untaint($path, qr(.*));
- my $archive_name = App::BorgRestore::Helper::untaint_archive_name($archive->{archive});
+ $destination = untaint($destination, qr(.*));
+ $path = untaint($path, qr(.*));
$log->infof("Restoring %s to %s from archive %s", $path, $destination, $archive->{archive});
@@ -343,10 +342,10 @@ method restore($path, $archive, $destination) {
my $workdir = pushd($destination, {untaint_pattern => qr{^(.*)$}});
my $final_destination = abs_path($basename);
- $final_destination = App::BorgRestore::Helper::untaint($final_destination, qr(.*));
+ $final_destination = untaint($final_destination, qr(.*));
$log->debugf("Removing %s", $final_destination);
File::Path::remove_tree($final_destination);
- $self->{deps}->{borg}->restore($components_to_strip, $archive_name, $path);
+ $self->{deps}->{borg}->restore($components_to_strip, $archive->{archive}, $path);
}
$log->debugf("CWD is %s", getcwd());
}
diff --git a/lib/App/BorgRestore/Borg.pm b/lib/App/BorgRestore/Borg.pm
index def6b9c..9884b21 100644
--- a/lib/App/BorgRestore/Borg.pm
+++ b/lib/App/BorgRestore/Borg.pm
@@ -2,7 +2,7 @@ package App::BorgRestore::Borg;
use v5.14;
use strictures 2;
-use App::BorgRestore::Helper;
+use App::BorgRestore::Helper qw(untaint);
use autodie;
use Date::Parse;
@@ -55,7 +55,14 @@ method borg_list() {
my @archives;
my $backup_prefix = $self->{backup_prefix};
- if (Version::Compare::version_compare($self->{borg_version}, "1.1") >= 0) {
+ if (Version::Compare::version_compare($self->{borg_version}, "1.2") >= 0) {
+ $log->debug("Getting archive list via json");
+ run [qw(borg list --glob-archives), "$backup_prefix*", qw(--json), $self->{borg_repo}], '>', \my $output or die $log->error("borg list returned $?")."\n";
+ my $json = decode_json($output);
+ for my $archive (@{$json->{archives}}) {
+ push @archives, $archive->{archive};
+ }
+ } elsif (Version::Compare::version_compare($self->{borg_version}, "1.1") >= 0) {
$log->debug("Getting archive list via json");
run [qw(borg list --prefix), $backup_prefix, qw(--json), $self->{borg_repo}], '>', \my $output or die $log->error("borg list returned $?")."\n";
my $json = decode_json($output);
@@ -116,6 +123,7 @@ method borg_list_time() {
method restore($components_to_strip, $archive_name, $path) {
$log->debugf("Restoring '%s' from archive %s, stripping %d components of the path", $path, $archive_name, $components_to_strip);
+ $archive_name = untaint($archive_name, qr(.*));
system(qw(borg extract -v --strip-components), $components_to_strip, $self->{borg_repo}."::".$archive_name, $path);
}
diff --git a/lib/App/BorgRestore/DB.pm b/lib/App/BorgRestore/DB.pm
index fd8506f..2999eb4 100644
--- a/lib/App/BorgRestore/DB.pm
+++ b/lib/App/BorgRestore/DB.pm
@@ -2,7 +2,7 @@ package App::BorgRestore::DB;
use v5.14;
use strictures 2;
-use App::BorgRestore::Helper;
+use App::BorgRestore::Helper qw(untaint);
use autodie;
use DBI;
@@ -29,13 +29,11 @@ method new($class: $db_path, $cache_size) {
if ($db_path =~ /^:/) {
$self->_open_db($db_path);
- $self->initialize_db();
} elsif (! -f $db_path) {
# ensure the cache directory exists
path($db_path)->parent->mkpath({mode => oct(700)});
$self->_open_db($db_path);
- $self->initialize_db();
} else {
$self->_open_db($db_path);
}
@@ -48,16 +46,108 @@ method _open_db($dbfile) {
$log->debugf("Opening database at %s", $dbfile);
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$dbfile","","", {RaiseError => 1, Taint => 1});
$self->{dbh}->do("PRAGMA strict=ON");
+
+ $self->_migrate();
}
method set_cache_size() {
$self->{dbh}->do("PRAGMA cache_size=-".$self->{cache_size});
}
-method initialize_db() {
- $log->debug("Creating initial database");
- $self->{dbh}->do('create table `files` (`path` text, primary key (`path`)) without rowid;');
- $self->{dbh}->do('create table `archives` (`archive_name` text unique);');
+method _migrate() {
+ my $version = $self->_get_db_version();
+ $log->debugf("Current database schema version: %s", $version);
+
+ my $schema = {
+ 1 => sub {
+ $self->{dbh}->do('create table if not exists `files` (`path` text, primary key (`path`)) without rowid;');
+ $self->{dbh}->do('create table if not exists `archives` (`archive_name` text unique);');
+ },
+ 2 => sub {
+ $self->{dbh}->do('alter table `archives` rename to `archives_old`');
+ $self->{dbh}->do('create table `archives` (`id` integer primary key autoincrement, `archive_name` text unique);');
+ $self->{dbh}->do('insert into `archives` select null, * from `archives_old`');
+ $self->{dbh}->do('drop table `archives_old`');
+
+ my $st = $self->{dbh}->prepare("select `archive_name` from `archives`;");
+ $st->execute();
+ while (my $result = $st->fetchrow_hashref) {
+ my $archive = $result->{archive_name};
+ # We trust all values here since they have already been
+ # sucessfully put into the DB previously. Thus they must be
+ # safe to deal with.
+ $archive = untaint($archive, qr(.*));
+ my $archive_id = $self->get_archive_id($archive);
+ $self->{dbh}->do("alter table `files` rename column `timestamp-$archive` to `$archive_id`");
+ }
+ },
+ 3 => sub {
+ # Drop all cached files due to a bug in
+ # lib/App/BorgRestore/PathTimeTable/DB.pm that caused certain files
+ # to be skipped rather than being added to the `files` table.
+ $self->{dbh}->do('delete from `archives`');
+ $self->{dbh}->do('delete from `files`');
+ },
+ 4 => sub {
+ # Drop all cached files due to a bug in
+ # lib/App/BorgRestore/PathTimeTable/DB.pm that caused certain files
+ # to be skipped rather than being added to the `files` table.
+ $self->{dbh}->do('delete from `archives`');
+ $self->{dbh}->do('delete from `files`');
+ },
+ 5 => sub {
+ # Remove columns left over by migrations 3 and 4 from files tables
+ my @archive_ids;
+ my $st = $self->{dbh}->prepare("select `id` from `archives`;");
+ $st->execute();
+ while (my $result = $st->fetchrow_hashref) {
+ push @archive_ids, $result->{id};
+ }
+
+ $self->{dbh}->do('create table `files_new` (`path` text, primary key (`path`)) without rowid;');
+ for my $archive_id (@archive_ids) {
+ $archive_id = untaint($archive_id, qr(.*));
+ $self->{dbh}->do('alter table `files_new` add column `'.$archive_id.'` integer;');
+ }
+
+ if (@archive_ids > 0) {
+ my @columns_to_copy = map {'`'.$_.'`'} @archive_ids;
+ @columns_to_copy = ('`path`', @columns_to_copy);
+ $self->{dbh}->do('insert into `files_new` select '.join(',', @columns_to_copy).' from files');
+ }
+
+ $self->{dbh}->do('drop table `files`');
+ $self->{dbh}->do('alter table `files_new` rename to `files`');
+ },
+ };
+ my $ran_migrations = 0;
+
+ for my $target_version (sort { $a <=> $b } keys %$schema) {
+ if ($version < $target_version) {
+ $log->debugf("Migrating to schema version %s", $target_version);
+ $self->{dbh}->begin_work();
+ $schema->{$target_version}->();
+ $self->_set_db_version($target_version);
+ $self->{dbh}->commit();
+ $log->debugf("Schema upgrade to version %s complete", $target_version);
+ $ran_migrations = 1;
+ }
+ }
+ if ($ran_migrations) {
+ $log->debug("Vacuuming database");
+ $self->{dbh}->do("vacuum");
+ }
+}
+
+method _get_db_version() {
+ my $st = $self->{dbh}->prepare("pragma user_version");
+ $st->execute();
+ return ($st->fetchrow_array)[0];
+}
+
+method _set_db_version($version) {
+ die 'Invalid version number' unless $version =~ m/^\d+$/;
+ my $st = $self->{dbh}->do("pragma user_version=$version");
}
method get_archive_names() {
@@ -79,22 +169,18 @@ method get_archive_row_count() {
}
method add_archive_name($archive) {
- $archive = App::BorgRestore::Helper::untaint_archive_name($archive);
-
my $st = $self->{dbh}->prepare('insert into `archives` (`archive_name`) values (?);');
- $st->execute($archive);
+ $st->execute(untaint($archive, qr(.*)));
$self->_add_column_to_table("files", $archive);
}
method _add_column_to_table($table, $column) {
- my $st = $self->{dbh}->prepare('alter table `'.$table.'` add column `'._prefix_archive_id($column).'` integer;');
+ my $st = $self->{dbh}->prepare('alter table `'.$table.'` add column `'.$self->get_archive_id($column).'` integer;');
$st->execute();
}
method remove_archive($archive) {
- $archive = App::BorgRestore::Helper::untaint_archive_name($archive);
-
my $archive_id = $self->get_archive_id($archive);
my @keep_archives = grep {$_ ne $archive;} @{$self->get_archive_names()};
@@ -104,7 +190,7 @@ method remove_archive($archive) {
$self->_add_column_to_table("files_new", $archive);
}
- my @columns_to_copy = map {'`'._prefix_archive_id($_).'`'} @keep_archives;
+ my @columns_to_copy = map {'`'.$self->get_archive_id($_).'`'} @keep_archives;
my @timestamp_columns_to_copy = @columns_to_copy;
@columns_to_copy = ('`path`', @columns_to_copy);
@@ -126,22 +212,20 @@ method remove_archive($archive) {
}
my $st = $self->{dbh}->prepare('delete from `archives` where `archive_name` = ?;');
- $st->execute($archive);
-}
-
-fun _prefix_archive_id($archive) {
- $archive = App::BorgRestore::Helper::untaint_archive_name($archive);
-
- return 'timestamp-'.$archive;
+ $st->execute(untaint($archive, qr(.*)));
}
method get_archive_id($archive) {
- return _prefix_archive_id($archive);
+ my $st = $self->{dbh}->prepare("select `id` from `archives` where `archive_name` = ?;");
+ $archive = untaint($archive, qr(.*));
+ $st->execute($archive);
+ my $result = $st->fetchrow_hashref;
+ return untaint($result->{id}, qr(.*));
}
method get_archives_for_path($path) {
my $st = $self->{dbh}->prepare('select * from `files` where `path` = ?;');
- $st->execute(App::BorgRestore::Helper::untaint($path, qr(.*)));
+ $st->execute(untaint($path, qr(.*)));
my @ret;
diff --git a/lib/App/BorgRestore/Helper.pm b/lib/App/BorgRestore/Helper.pm
index 869d4ee..383a49f 100644
--- a/lib/App/BorgRestore/Helper.pm
+++ b/lib/App/BorgRestore/Helper.pm
@@ -3,9 +3,12 @@ use v5.14;
use strictures 2;
use autodie;
+use Exporter 'import';
use Function::Parameters;
use POSIX ();
+our @EXPORT_OK = qw(untaint);
+
=encoding utf-8
=head1 NAME
@@ -23,10 +26,6 @@ fun untaint($data, $regex) {
return $1;
}
-fun untaint_archive_name($archive) {
- return untaint($archive, qr([a-zA-Z0-9-:+\.]+));
-}
-
fun format_timestamp($timestamp) {
return POSIX::strftime "%a. %F %H:%M:%S %z", localtime $timestamp;
}
diff --git a/lib/App/BorgRestore/PathTimeTable/DB.pm b/lib/App/BorgRestore/PathTimeTable/DB.pm
index 221063c..685c7b9 100644
--- a/lib/App/BorgRestore/PathTimeTable/DB.pm
+++ b/lib/App/BorgRestore/PathTimeTable/DB.pm
@@ -1,6 +1,7 @@
package App::BorgRestore::PathTimeTable::DB;
use strictures 2;
+use Carp::Assert;
use Function::Parameters;
BEGIN {
use Log::Any qw($log);
@@ -50,18 +51,46 @@ method set_archive_id($archive_id) {
}
method add_path($path, $time) {
+ $log->tracef("Adding path to cache: %s", $path) if TRACE;
$self->{stats}->{total_paths}++;
my $old_cache_path = $self->{current_path_in_cache};
+ # Check if the new path requires us to (partially) invalidate our cache and
+ # add any files/directories to the database. If the new path is a subpath
+ # (substring actually) of the cached path, we can keep it only in the cache
+ # and no flush is needed. Otherwise we need to flush all parts of the path
+ # that are no longer contained in the new path.
+ #
+ # We start by checking the currently cached path ($old_cache_path) against
+ # the new $path. Then we remove one part from the path at a time, until we
+ # reach a parent path (directory) of $path.
+ $log->tracef("Checking if cache invalidation is required") if TRACE;
while ((my $slash_index = rindex($old_cache_path, "/")) != -1) {
$self->{stats}->{cache_invalidation_loop_iterations}++;
- if ($old_cache_path eq substr($path, 0, length($old_cache_path))) {
+ # Directories in the borg output cannot be differentiated by their
+ # path, since their path looks just like a file path. I.e. there is no
+ # directory separator (/) at the end of a directory path.
+ #
+ # Since we want to keep any directory in our cache, if it contains
+ # $path, we can treat any cached path as a directory path. If the
+ # cached path was really a directory, the new $path will also contain a
+ # directory separator (/) between the old cached path (the parent
+ # directory) and the new path (a subdirectory or a file in the
+ # directory). If the cached path was not actually a directory,
+ # but a file, the new path cannot match the old one because a file name
+ # cannot contain a directory separator.
+ my $cache_check_path = $old_cache_path.'/';
+ $log->tracef("Checking if cached path '%s' contains '%s'", $cache_check_path, $path) if TRACE;
+ if ($cache_check_path eq substr($path, 0, length($cache_check_path))) {
+ $log->tracef("Cache path '%s' is a parent directory of new path '%s'", $old_cache_path, $path) if TRACE;
# keep the cache content for the part of the path that stays the same
last;
}
+ $log->tracef("Cached path '%s' requires flush to database", $old_cache_path) if TRACE;
my $removed_time = delete $self->{cache}->{$old_cache_path};
$self->_add_path_to_db($self->{archive_id}, $old_cache_path, $removed_time);
# strip last part of path
$old_cache_path = substr($old_cache_path, 0, $slash_index);
+ $log->tracef("Changed cache path to parent directory: %s", $old_cache_path) if TRACE;
# update parent timestamp
my $cached = $self->{cache}->{$old_cache_path};
@@ -70,10 +99,12 @@ method add_path($path, $time) {
$self->{cache}->{$old_cache_path} = $removed_time;
}
}
+ $log->tracef("Cache invalidation complete") if TRACE;
- if ($old_cache_path ne substr($path, 0, length($old_cache_path))) {
+ my $cache_check_path = $old_cache_path.'/';
+ if ($cache_check_path ne substr($path, 0, length($cache_check_path))) {
# ensure that top level directory is also written
- $self->_add_path_to_db($self->{archive_id}, $old_cache_path, $self->{cache}->{$old_cache_path}) unless $old_cache_path eq ".";
+ $self->_add_path_to_db($self->{archive_id}, $old_cache_path, $self->{cache}->{$old_cache_path}) unless ($old_cache_path eq "." or $old_cache_path eq '');
}
my $cached = $self->{cache}->{$path};
@@ -100,6 +131,11 @@ method save_nodes() {
for my $key (keys %{$self->{stats}}) {
$log->debugf("Performance counter %s = %s", $key, $self->{stats}->{$key});
}
+
+ # +2 because:
+ # - borg list gives us `.` as the first path and we essentially skip it
+ # - we call `add_path` with `.` at the beginning of this method
+ assert($self->{stats}->{real_calls_to_db_class} + 2 == $self->{stats}->{total_paths}, "All files were actually added to the database");
}
1;
diff --git a/lib/App/BorgRestore/Settings.pm b/lib/App/BorgRestore/Settings.pm
index bc4fbd0..a1d6bfd 100644
--- a/lib/App/BorgRestore/Settings.pm
+++ b/lib/App/BorgRestore/Settings.pm
@@ -2,7 +2,7 @@ package App::BorgRestore::Settings;
use v5.14;
use strictures 2;
-use App::BorgRestore::Helper;
+use App::BorgRestore::Helper qw(untaint);
use autodie;
use Function::Parameters;
@@ -158,7 +158,7 @@ method new_no_defaults($class: $deps = {}) {
."the path in the config file or ensure that the variables are set.";
}
- $cache_path_base = App::BorgRestore::Helper::untaint($cache_path_base, qr/.*/);
+ $cache_path_base = untaint($cache_path_base, qr/.*/);
return $self;
}
@@ -188,7 +188,7 @@ fun load_config_files() {
push @configfiles, "/etc/borg-restore.cfg";
for my $configfile (@configfiles) {
- $configfile = App::BorgRestore::Helper::untaint($configfile, qr/.*/);
+ $configfile = untaint($configfile, qr/.*/);
if (-e $configfile) {
unless (my $return = do $configfile) {
die "couldn't parse $configfile: $@" if $@;
diff --git a/script/borg-restore.pl b/script/borg-restore.pl
index f03fa22..0e67439 100755
--- a/script/borg-restore.pl
+++ b/script/borg-restore.pl
@@ -138,7 +138,7 @@ For configuration options please see L<App::BorgRestore::Settings>.
=head1 LICENSE
-Copyright (C) 2016-2018 Florian Pritz <bluewind@xinu.at>
+Copyright (C) 2016-2023 Florian Pritz <bluewind@xinu.at>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/t/handle_added_archives_with_db.t b/t/handle_added_archives_with_db.t
index 6fd94ea..40f79c7 100644
--- a/t/handle_added_archives_with_db.t
+++ b/t/handle_added_archives_with_db.t
@@ -23,6 +23,7 @@ for my $in_memory (0,1) {
$cb->("XXX, 1970-01-01 00:00:10 boot");
$cb->("XXX, 1970-01-01 00:00:20 boot/grub");
$cb->("XXX, 1970-01-01 00:00:08 boot/grub/grub.cfg");
+ $cb->("XXX, 1970-01-01 00:00:09 boot/grub/grub.cfg.sig");
$cb->("XXX, 1970-01-01 00:00:13 boot/foo");
$cb->("XXX, 1970-01-01 00:00:13 boot/foo/blub");
$cb->("XXX, 1970-01-01 00:00:19 boot/foo/bar");
@@ -31,6 +32,7 @@ for my $in_memory (0,1) {
$cb->("XXX, 1970-01-01 00:00:04 boot/test1/f2");
$cb->("XXX, 1970-01-01 00:00:03 boot/test1/f3");
$cb->("XXX, 1970-01-01 00:00:02 boot/test1/f4");
+ $cb->("XXX, 1970-01-01 00:00:10 boot1");
$cb->("XXX, 1970-01-01 00:00:03 etc");
$cb->("XXX, 1970-01-01 00:00:02 etc/foo");
$cb->("XXX, 1970-01-01 00:00:01 etc/foo/bar");
@@ -42,7 +44,7 @@ for my $in_memory (0,1) {
$app->_handle_added_archives(['archive-1']);
# check database content
- is($db->get_archive_row_count(), 15, 'correct row count (15 paths with timestamps)');
+ is($db->get_archive_row_count(), 17, 'correct row count (17 paths with timestamps)');
eq_or_diff($db->get_archives_for_path('.'), [{archive => 'archive-1', modification_time => undef},]);
eq_or_diff($db->get_archives_for_path('boot'), [{archive => 'archive-1', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/foo'), [{archive => 'archive-1', modification_time => 19},]);
@@ -50,11 +52,13 @@ for my $in_memory (0,1) {
eq_or_diff($db->get_archives_for_path('boot/foo/blub'), [{archive => 'archive-1', modification_time => 13},]);
eq_or_diff($db->get_archives_for_path('boot/grub'), [{archive => 'archive-1', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg'), [{archive => 'archive-1', modification_time => 8},]);
+ eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg.sig'), [{archive => 'archive-1', modification_time => 9},]);
eq_or_diff($db->get_archives_for_path('boot/test1'), [{archive => 'archive-1', modification_time => 4},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f1'), [{archive => 'archive-1', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f2'), [{archive => 'archive-1', modification_time => 4},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f3'), [{archive => 'archive-1', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f4'), [{archive => 'archive-1', modification_time => 2},]);
+ eq_or_diff($db->get_archives_for_path('boot1'), [{archive => 'archive-1', modification_time => 10},]);
eq_or_diff($db->get_archives_for_path('etc'), [{archive => 'archive-1', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('etc/foo'), [{archive => 'archive-1', modification_time => 2},]);
eq_or_diff($db->get_archives_for_path('etc/foo/bar'), [{archive => 'archive-1', modification_time => 1},]);
@@ -70,6 +74,7 @@ for my $in_memory (0,1) {
$cb->("XXX, 1970-01-01 00:00:10 boot");
$cb->("XXX, 1970-01-01 00:00:20 boot/grub");
$cb->("XXX, 1970-01-01 00:00:08 boot/grub/grub.cfg");
+ $cb->("XXX, 1970-01-01 00:00:09 boot/grub/grub.cfg.sig");
$cb->("XXX, 1970-01-01 00:00:13 boot/foo");
$cb->("XXX, 1970-01-01 00:00:13 boot/foo/blub");
$cb->("XXX, 1970-01-01 00:00:19 boot/foo/bar");
@@ -79,6 +84,7 @@ for my $in_memory (0,1) {
$cb->("XXX, 1970-01-01 00:00:03 boot/test1/f3");
$cb->("XXX, 1970-01-01 00:00:02 boot/test1/f4");
$cb->("XXX, 1970-01-01 00:00:07 boot/test1/f5");
+ $cb->("XXX, 1970-01-01 00:00:10 boot1");
$cb->("XXX, 1970-01-01 00:00:03 etc");
$cb->("XXX, 1970-01-01 00:00:02 etc/foo");
$cb->("XXX, 1970-01-01 00:00:01 etc/foo/bar");
@@ -86,7 +92,7 @@ for my $in_memory (0,1) {
$app->_handle_added_archives(['archive-2']);
# check database content
- is($db->get_archive_row_count(), 16, 'correct row count (16 paths with timestamps)');
+ is($db->get_archive_row_count(), 18, 'correct row count (18 paths with timestamps)');
eq_or_diff($db->get_archives_for_path('.'), [
{archive => 'archive-1', modification_time => undef},
{archive => 'archive-2', modification_time => undef},
@@ -115,6 +121,10 @@ for my $in_memory (0,1) {
{archive => 'archive-1', modification_time => 8},
{archive => 'archive-2', modification_time => 8},
]);
+ eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg.sig'), [
+ {archive => 'archive-1', modification_time => 9},
+ {archive => 'archive-2', modification_time => 9},
+ ]);
eq_or_diff($db->get_archives_for_path('boot/test1'), [
{archive => 'archive-1', modification_time => 4},
{archive => 'archive-2', modification_time => 7},
@@ -139,6 +149,10 @@ for my $in_memory (0,1) {
{archive => 'archive-1', modification_time => undef},
{archive => 'archive-2', modification_time => 7},
]);
+ eq_or_diff($db->get_archives_for_path('boot1'), [
+ {archive => 'archive-1', modification_time => 10},
+ {archive => 'archive-2', modification_time => 10},
+ ]);
eq_or_diff($db->get_archives_for_path('etc'), [
{archive => 'archive-1', modification_time => 3},
{archive => 'archive-2', modification_time => 3},
@@ -164,7 +178,7 @@ for my $in_memory (0,1) {
$app->_handle_removed_archives(['archive-2']);
# check database contents
- is($db->get_archive_row_count(), 15, 'correct row count (15 paths with timestamps)');
+ is($db->get_archive_row_count(), 17, 'correct row count (17 paths with timestamps)');
eq_or_diff($db->get_archives_for_path('.'), [{archive => 'archive-2', modification_time => undef},]);
eq_or_diff($db->get_archives_for_path('boot'), [{archive => 'archive-2', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/foo'), [{archive => 'archive-2', modification_time => 19},]);
@@ -172,12 +186,14 @@ for my $in_memory (0,1) {
eq_or_diff($db->get_archives_for_path('boot/foo/blub'), [{archive => 'archive-2', modification_time => 13},]);
eq_or_diff($db->get_archives_for_path('boot/grub'), [{archive => 'archive-2', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg'), [{archive => 'archive-2', modification_time => 8},]);
+ eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg.sig'), [{archive => 'archive-2', modification_time => 9},]);
eq_or_diff($db->get_archives_for_path('boot/test1'), [{archive => 'archive-2', modification_time => 7},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f1'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f2'), [{archive => 'archive-2', modification_time => 5},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f3'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f4'), [{archive => 'archive-2', modification_time => 2},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f5'), [{archive => 'archive-2', modification_time => 7},]);
+ eq_or_diff($db->get_archives_for_path('boot1'), [{archive => 'archive-2', modification_time => 10},]);
eq_or_diff($db->get_archives_for_path('etc'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('etc/foo'), [{archive => 'archive-2', modification_time => 2},]);
eq_or_diff($db->get_archives_for_path('etc/foo/bar'), [{archive => 'archive-2', modification_time => 1},]);
@@ -186,7 +202,7 @@ for my $in_memory (0,1) {
# run remove again. shouldn't change anything
$app->_handle_removed_archives(['archive-2']);
- is($db->get_archive_row_count(), 15, 'correct row count (15 paths with timestamps)');
+ is($db->get_archive_row_count(), 17, 'correct row count (17 paths with timestamps)');
eq_or_diff($db->get_archives_for_path('.'), [{archive => 'archive-2', modification_time => undef},]);
eq_or_diff($db->get_archives_for_path('boot'), [{archive => 'archive-2', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/foo'), [{archive => 'archive-2', modification_time => 19},]);
@@ -194,12 +210,14 @@ for my $in_memory (0,1) {
eq_or_diff($db->get_archives_for_path('boot/foo/blub'), [{archive => 'archive-2', modification_time => 13},]);
eq_or_diff($db->get_archives_for_path('boot/grub'), [{archive => 'archive-2', modification_time => 20},]);
eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg'), [{archive => 'archive-2', modification_time => 8},]);
+ eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg.sig'), [{archive => 'archive-2', modification_time => 9},]);
eq_or_diff($db->get_archives_for_path('boot/test1'), [{archive => 'archive-2', modification_time => 7},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f1'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f2'), [{archive => 'archive-2', modification_time => 5},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f3'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f4'), [{archive => 'archive-2', modification_time => 2},]);
eq_or_diff($db->get_archives_for_path('boot/test1/f5'), [{archive => 'archive-2', modification_time => 7},]);
+ eq_or_diff($db->get_archives_for_path('boot1'), [{archive => 'archive-2', modification_time => 10},]);
eq_or_diff($db->get_archives_for_path('etc'), [{archive => 'archive-2', modification_time => 3},]);
eq_or_diff($db->get_archives_for_path('etc/foo'), [{archive => 'archive-2', modification_time => 2},]);
eq_or_diff($db->get_archives_for_path('etc/foo/bar'), [{archive => 'archive-2', modification_time => 1},]);
@@ -218,12 +236,14 @@ for my $in_memory (0,1) {
eq_or_diff($db->get_archives_for_path('boot/foo/blub'), []);
eq_or_diff($db->get_archives_for_path('boot/grub'), []);
eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg'), []);
+ eq_or_diff($db->get_archives_for_path('boot/grub/grub.cfg.sig'), []);
eq_or_diff($db->get_archives_for_path('boot/test1'), []);
eq_or_diff($db->get_archives_for_path('boot/test1/f1'), []);
eq_or_diff($db->get_archives_for_path('boot/test1/f2'), []);
eq_or_diff($db->get_archives_for_path('boot/test1/f3'), []);
eq_or_diff($db->get_archives_for_path('boot/test1/f4'), []);
eq_or_diff($db->get_archives_for_path('boot/test1/f5'), []);
+ eq_or_diff($db->get_archives_for_path('boot1'), []);
eq_or_diff($db->get_archives_for_path('etc'), []);
eq_or_diff($db->get_archives_for_path('etc/foo'), []);
eq_or_diff($db->get_archives_for_path('etc/foo/bar'), []);
diff --git a/t/helper/untaint.t b/t/helper/untaint.t
deleted file mode 100644
index 0c2e36a..0000000
--- a/t/helper/untaint.t
+++ /dev/null
@@ -1,19 +0,0 @@
-use strictures 2;
-
-use Log::Any::Adapter ('TAP');
-use Test::More;
-use Test::Exception;
-
-use App::BorgRestore::Helper;
-
-ok(App::BorgRestore::Helper::untaint_archive_name('abc-1234:5+1') eq 'abc-1234:5+1');
-ok(App::BorgRestore::Helper::untaint_archive_name('abc') eq 'abc');
-ok(App::BorgRestore::Helper::untaint_archive_name('root-2016-09-30T15+02:00.checkpoint') eq 'root-2016-09-30T15+02:00.checkpoint');
-
-dies_ok(sub{App::BorgRestore::Helper::untaint_archive_name('abc`"\'')}, 'special chars not allowed');
-dies_ok(sub{App::BorgRestore::Helper::untaint_archive_name('abc`')}, 'special chars not allowed');
-dies_ok(sub{App::BorgRestore::Helper::untaint_archive_name('abc"')}, 'special chars not allowed');
-dies_ok(sub{App::BorgRestore::Helper::untaint_archive_name('abc\'')}, 'special chars not allowed');
-
-
-done_testing;