From dcb0c5aa288256f5a5ac4bb795a59ce1bbfdad06 Mon Sep 17 00:00:00 2001 From: Axel Burri Date: Fri, 30 Jun 2017 17:07:36 +0200 Subject: [PATCH] contrib/migration/raw_suffix2sidecar: add migration tool for creating raw sidecar files from uuid-suffixed raw backup files --- ChangeLog | 3 + Makefile | 1 + contrib/migration/raw_suffix2sidecar | 181 +++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100755 contrib/migration/raw_suffix2sidecar diff --git a/ChangeLog b/ChangeLog index 67dc38d..0770bae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ btrbk-current + * MIGRATION + - If you are using raw targets, make sure to run the + "raw_suffix2sidecar" utility in each target directory. * Add "resume" command, replacement for "-r, --resume-only" command line option (which is now deprecated). * Add "snapshot" command (close #150). diff --git a/Makefile b/Makefile index 8ed0db9..6f0ba17 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ install-share: @echo 'installing auxiliary scripts...' install -pDm755 ssh_filter_btrbk.sh "$(DESTDIR)$(SCRIPTDIR)/ssh_filter_btrbk.sh" install -pDm755 contrib/cron/btrbk-mail "$(DESTDIR)$(SCRIPTDIR)/btrbk-mail" + install -pDm755 contrib/migration/raw_suffix2sidecar "$(DESTDIR)$(SCRIPTDIR)/raw_suffix2sidecar" install-man: @echo 'installing manpages...' diff --git a/contrib/migration/raw_suffix2sidecar b/contrib/migration/raw_suffix2sidecar new file mode 100755 index 0000000..ae15362 --- /dev/null +++ b/contrib/migration/raw_suffix2sidecar @@ -0,0 +1,181 @@ +#!/usr/bin/perl +# +# raw_suffix2sidecar - migrate to btrbk raw target sidecar files +# +# Copyright (C) 2017 Axel Burri +# +# 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# --------------------------------------------------------------------- +# The official btrbk website is located at: +# http://digint.ch/btrbk/ +# +# Author: +# Axel Burri +# --------------------------------------------------------------------- + +# Create raw sidecar ".info" files from uuid-suffixed raw backup files +# generated by btrbk < v0.26.0. + +use strict; +use warnings FATAL => qw( all ); +use Getopt::Long qw(GetOptions); + +our $VERSION = '0.26.0-dev'; # match btrbk version +our $AUTHOR = 'Axel Burri '; +our $PROJECT_HOME = ''; + +my $VERSION_INFO = "raw_suffix2sidecar (btrbk migration script), version $VERSION"; + +my $compress_format_alt = 'gz|bz2|xz|lzo|lz4'; +my $file_match = qr/[0-9a-zA-Z_@\+\-\.\/]+/; # note: ubuntu uses '@' in the subvolume layout: +my $uuid_match = qr/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; +my $timestamp_postfix_match = qr/\.(?[0-9]{4})(?[0-9]{2})(?
[0-9]{2})(T(?[0-9]{2})(?[0-9]{2})((?[0-9]{2})(?(Z|[+-][0-9]{4})))?)?(_(?[0-9]+))?/; # matches "YYYYMMDD[Thhmm[ss+0000]][_NN]" +my $raw_postfix_match = qr/--(?$uuid_match)(\@(?$uuid_match))?\.btrfs?(\.(?($compress_format_alt)))?(\.(?gpg))?(\.(?split_aa))?(\.(?part))?/; # matches ".btrfs_[@][.gz|bz2|xz][.gpg][.split_aa][.part]" + +my $dryrun; + +my %raw_info_sort = ( + TYPE => 1, + FILE => 2, + RECEIVED_UUID => 3, + RECEIVED_PARENT_UUID => 4, + INCOMPLETE => 5, + compress => 9, + split => 10, + encrypt => 11, + ); + +sub VERSION_MESSAGE +{ + print STDERR $VERSION_INFO . "\n\n"; +} + +sub HELP_MESSAGE +{ + print STDERR "usage: raw_suffix2sidecar ...\n"; + print STDERR "\n"; + print STDERR "options:\n"; + # "--------------------------------------------------------------------------------"; # 80 + print STDERR " -h, --help display this help message\n"; + print STDERR " --version display version information\n"; + print STDERR " -n, --dry-run perform a trial run with no changes made\n"; + print STDERR "\n"; + print STDERR "For additional information, see $PROJECT_HOME\n"; +} + +sub write_raw_info($$) +{ + my $file = shift // die; + my $raw_info = shift // die; + + my $info_file = $file . '.info'; + my @line; + push @line, "#raw_suffix2sidecar-v$VERSION"; + push @line, "# Do not edit this file"; + + # sort by %raw_info_sort, then by key + foreach(sort { (($raw_info_sort{$a} || 99) <=> ($raw_info_sort{$b} || 99)) || ($a cmp $b) } keys %$raw_info) { + push @line, ($_ . '=' . $raw_info->{$_}) if($raw_info->{$_}); + } + + print "Creating info file: $info_file\n"; + + unless($dryrun) { + open (INFOFILE, ">> $info_file") || die "Failed to open $info_file"; + print INFOFILE join("\n", @line) . "\n"; + close(INFOFILE); + } + + return $info_file; +} + + +MAIN: +{ + Getopt::Long::Configure qw(gnu_getopt); + unless(GetOptions( + 'help|h' => sub { VERSION_MESSAGE(); HELP_MESSAGE(0); exit 0; }, + 'version' => sub { VERSION_MESSAGE(); exit 0; }, + 'dry-run|n' => \$dryrun, + )) + { + VERSION_MESSAGE(); + HELP_MESSAGE(0); + exit 2; + } + unless(@ARGV) { + VERSION_MESSAGE(); + HELP_MESSAGE(); + exit 1; + } + + foreach my $target_dir (@ARGV) { + $target_dir =~ s/\/+$//; + print "Processing directory: $target_dir/\n"; + opendir(my($dh), $target_dir) || die "Failed to open directory '$target_dir': $!"; + my @files = readdir($dh); + closedir $dh; + + my @splitfiles = @files; + foreach my $file (@files) { + if($file =~ /^(?$file_match$timestamp_postfix_match)$raw_postfix_match$/) { + print "\nProcessing raw backup: $file\n"; + + my $newname = $+{basename} || die; + my %raw_info = ( + TYPE => 'raw', + RECEIVED_UUID => $+{received_uuid}, + RECEIVED_PARENT_UUID => $+{parent_uuid}, + INCOMPLETE => $+{incomplete} ? 1 : 0, + compress => $+{compress}, + split => ($+{split} ? (-s $file) : undef), # file size + encrypt => $+{encrypt}, + ); + die "Missing received uuid in file: $file" unless $raw_info{RECEIVED_UUID}; + $newname .= '.btrfs'; + $newname .= '.' . $raw_info{compress} if($raw_info{compress}); + $newname .= '.' . $raw_info{encrypt} if($raw_info{encrypt}); + $raw_info{FILE} = $newname; + write_raw_info("$target_dir/$newname", \%raw_info); + + if($raw_info{split}) { + my $sfile = $file; + $sfile =~ s/_aa$//; # we match on ".split_aa" above + foreach my $splitfile (@splitfiles) { + if($splitfile =~ /^${sfile}(_[a-z]+)$/) { + my $suffix = $1 // die; + print "Renaming file: $target_dir/$splitfile -> $target_dir/$newname.split$suffix\n"; + unless($dryrun) { + rename("$target_dir/$splitfile", "$target_dir/$newname.split$suffix") || die "Failed to rename file: $target_dir/$splitfile -> $target_dir/${newname}.split$suffix: $!"; + } + } + } + } + else { + print "Renaming file: $target_dir/$file -> $target_dir/$newname\n"; + unless($dryrun) { + rename("$target_dir/$file", "$target_dir/$newname") || die "Failed to rename file: $target_dir/$file -> $target_dir/$newname"; + } + } + } + } + } + + if($dryrun) { + print "\nNOTE: Dryrun was active, none of the operations above were actually executed!\n"; + } +} + +1;