App-Yabsm

 view release on metacpan or  search on metacpan

lib/App/Yabsm/Backup/Local.pm  view on Meta::CPAN

#  Author:  Nicholas Hubbard
#  WWW:     https://github.com/NicholasBHubbard/yabsm
#  License: MIT

#  Provides the &do_local_backup subroutine, which performs a single local_backup
#  This is a top-level subroutine that is directly scheduled to be run by the
#  daemon.

use strict;
use warnings;
use v5.16.3;

package App::Yabsm::Backup::Local;

use App::Yabsm::Backup::Generic qw(take_tmp_snapshot
                                   take_bootstrap_snapshot
                                   the_local_bootstrap_snapshot
                                   bootstrap_lock_file
                                   create_bootstrap_lock_file
                                  );
use App::Yabsm::Snapshot qw(delete_snapshot sort_snapshots is_snapshot_name);
use App::Yabsm::Tools qw( :ALL );
use App::Yabsm::Config::Query qw( :ALL );

use File::Basename qw(basename);

use Exporter qw(import);
our @EXPORT_OK = qw(do_local_backup
                    do_local_backup_bootstrap
                    maybe_do_local_backup_bootstrap
                    the_remote_bootstrap_snapshot
                   );

                 ####################################
                 #            SUBROUTINES           #
                 ####################################

sub do_local_backup {

    # Perform a $tframe local_backup for $local_backup.

    arg_count_or_die(3, 3, @_);

    my $local_backup = shift;
    my $tframe       = shift;
    my $config_ref   = shift;

    # We can't perform a backup if the bootstrap process is currently being
    # performed.
    if (bootstrap_lock_file($local_backup, 'local', $config_ref)) {
        return undef;
    }

    my $backup_dir = local_backup_dir($local_backup, $tframe, $config_ref);

    unless (is_btrfs_dir($backup_dir) && -r $backup_dir) {
        my $username = getpwuid $<;
        die "yabsm: error: '$backup_dir' is not a directory residing on a btrfs filesystem that is readable by user '$username'\n";
    }

    my $tmp_snapshot       = take_tmp_snapshot($local_backup, 'local', $tframe, $config_ref);
    my $bootstrap_snapshot = maybe_do_local_backup_bootstrap($local_backup, $config_ref);

    system_or_die("sudo -n btrfs send -p '$bootstrap_snapshot' '$tmp_snapshot' | sudo -n btrfs receive '$backup_dir' >/dev/null 2>&1");

    # @backups is sorted from newest to oldest
    my @backups = sort_snapshots(do {
        opendir my $dh, $backup_dir or confess("yabsm: internal error: cannot opendir '$backup_dir'");
        my @backups = grep { is_snapshot_name($_) } readdir($dh);
        closedir $dh;
        map { $_ = "$backup_dir/$_" } @backups;
        \@backups;
    });
    my $num_backups = scalar @backups;
    my $to_keep     = local_backup_timeframe_keep($local_backup, $tframe, $config_ref);

    # There is 1 more backup than should be kept because we just performed a
    # backup.
    if ($num_backups == $to_keep + 1) {
        my $oldest = pop @backups;
        delete_snapshot($oldest);
    }
    # We have not reached the backup quota yet so we don't delete anything.
    elsif ($num_backups <= $to_keep) {
        ;
    }
    # User changed their settings to keep less backups than they were keeping
    # prior.
    else {
        for (; $num_backups > $to_keep; $num_backups--) {
            my $oldest = pop @backups;
            delete_snapshot($oldest);
        }
    }

    return "$backup_dir/" . basename($tmp_snapshot);
}

sub do_local_backup_bootstrap {

    # Perform the bootstrap phase of an incremental backup for $local_backup.

    arg_count_or_die(2, 2, @_);

    my $local_backup = shift;
    my $config_ref   = shift;

    if (bootstrap_lock_file($local_backup, 'local', $config_ref)) {
        return undef;
    }

    # The lock file will be deleted when $lock_fh goes out of scope (uses File::Temp).
    my $lock_fh = create_bootstrap_lock_file($local_backup, 'local', $config_ref);

    if (my $local_boot_snap = the_local_bootstrap_snapshot($local_backup, 'local', $config_ref)) {
        delete_snapshot($local_boot_snap);
    }
    if (my $remote_boot_snap = the_remote_bootstrap_snapshot($local_backup, $config_ref)) {
        delete_snapshot($remote_boot_snap);
    }

    my $local_boot_snap = take_bootstrap_snapshot($local_backup, 'local', $config_ref);

    my $backup_dir_base = local_backup_dir($local_backup, undef, $config_ref);

    system_or_die("sudo -n btrfs send '$local_boot_snap' | sudo -n btrfs receive '$backup_dir_base' >/dev/null 2>&1");

    return $local_boot_snap;
}

sub maybe_do_local_backup_bootstrap {

    # Like &do_local_backup_bootstrap but only perform the bootstrap if it hasn't
    # been performed yet.

    arg_count_or_die(2, 2, @_);

    my $local_backup = shift;
    my $config_ref   = shift;

    my $local_boot_snap  = the_local_bootstrap_snapshot($local_backup, 'local', $config_ref);
    my $remote_boot_snap = the_remote_bootstrap_snapshot($local_backup, $config_ref);

    unless ($local_boot_snap && $remote_boot_snap) {
        $local_boot_snap = do_local_backup_bootstrap($local_backup, $config_ref);
    }

    return $local_boot_snap;
}

sub the_remote_bootstrap_snapshot {

    # Return the remote bootstrap snapshot for $local_backup if it exists and
    # return undef otherwise. Die if we find multiple bootstrap snapshots.

    arg_count_or_die(2, 2, @_);

    my $local_backup = shift;
    my $config_ref   = shift;

    my $backup_dir_base = local_backup_dir($local_backup, undef, $config_ref);

    unless (-d $backup_dir_base && -r $backup_dir_base) {
        my $username = getpwuid $<;
        die "yabsm: error: no directory '$backup_dir_base' that is readable by user '$username'\n";
    }

    opendir my $dh, $backup_dir_base or confess("yabsm: internal error: cannot opendir '$backup_dir_base'");
    my @boot_snaps = grep { is_snapshot_name($_, ONLY_BOOTSTRAP => 1) } readdir($dh);
    closedir $dh;

    map { $_ = "$backup_dir_base/$_" } @boot_snaps;

    if (0 == @boot_snaps) {
        return undef;
    }
    elsif (1 == @boot_snaps) {
        return $boot_snaps[0];
    }
    else {
        die "yabsm: error: found multiple remote bootstrap snapshots for local_backup '$local_backup' in '$backup_dir_base'\n";
    }
}

1;



( run in 0.601 second using v1.01-cache-2.11-cpan-ceb78f64989 )