diff options
-rw-r--r-- | Changes | 29 | ||||
-rw-r--r-- | META.json | 15 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | cpanfile | 3 | ||||
-rw-r--r-- | lib/App/BorgRestore.pm | 13 | ||||
-rw-r--r-- | lib/App/BorgRestore/Borg.pm | 12 | ||||
-rw-r--r-- | lib/App/BorgRestore/DB.pm | 130 | ||||
-rw-r--r-- | lib/App/BorgRestore/Helper.pm | 7 | ||||
-rw-r--r-- | lib/App/BorgRestore/PathTimeTable/DB.pm | 42 | ||||
-rw-r--r-- | lib/App/BorgRestore/Settings.pm | 6 | ||||
-rwxr-xr-x | script/borg-restore.pl | 2 | ||||
-rw-r--r-- | t/handle_added_archives_with_db.t | 28 | ||||
-rw-r--r-- | t/helper/untaint.t | 19 |
13 files changed, 235 insertions, 77 deletions
@@ -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 @@ -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 } @@ -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 @@ -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; |