#!/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: # https://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'; # 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|bz3|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|bz3|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;