Subversion Repositories zfs_utils

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
47 rodolico 1
#!/usr/bin/env perl
2
 
3
# cleanSnaps - detect and remove old ZFS snapshots named like YYYY-MM-DD-<N><d|w|m>
4
# Usage: cleanSnaps [-n] [-f] [-v]
5
#   -n  dry-run (default)
6
#   -f  actually destroy snapshots
7
#   -v  verbose
8
 
9
use strict;
10
use warnings;
11
use Getopt::Std;
12
use Time::Piece;
13
 
14
my %opts;
15
getopts('nfv', \%opts);
16
 
17
my $DRY_RUN = $opts{f} ? 0 : 1;    # default dry-run, -f disables dry-run
18
my $FORCE = $opts{f} ? 1 : 0;
19
my $VERBOSE = $opts{v} ? 1 : 0;
20
 
21
my $ZFS_CMD = $ENV{ZFS_CMD} // 'zfs';
22
 
23
sub logmsg { print @_, "\n" if $VERBOSE }
24
 
25
# gather snapshots
26
my $now = time();
27
my @candidates;
28
 
29
open my $fh, '-|', "$ZFS_CMD list -H -o name -t snapshot" or die "Failed to run $ZFS_CMD: $!";
30
while (my $snap = <$fh>) {
31
    chomp $snap;
32
    next unless defined $snap && $snap =~ /\S/;
33
 
34
    unless ($snap =~ /@/) {
35
        logmsg("skipping invalid snapshot name: $snap");
36
        next;
37
    }
38
    my ($dataset, $snapname) = split /@/, $snap, 2;
39
 
40
    # match date + retention tag
41
    unless ($snapname =~ m/^(\d{4}-\d{2}-\d{2}).*(\d+)([dwm])$/) {
42
        logmsg("snapshot does not match YYYY-MM-DD-<N><d|w|m>: $snap");
43
        next;
44
    }
45
    my ($snap_date, $num, $unit) = ($1, $2, $3);
46
 
47
    # parse snapshot date using Time::Piece
48
    my $snap_epoch;
49
    eval {
50
        my $tp = Time::Piece->strptime($snap_date, '%Y-%m-%d');
51
        $snap_epoch = $tp->epoch;
52
        1;
53
    } or do {
54
        logmsg("failed to parse date $snap_date for $snap");
55
        next;
56
    };
57
 
58
    my $days;
59
    if ($unit eq 'd') { $days = $num }
60
    elsif ($unit eq 'w') { $days = $num * 7 }
61
    elsif ($unit eq 'm') { $days = $num * 30 }
62
    elsif ($unit eq 'y') { $days = $num * 365}
63
    else { logmsg("unknown unit $unit for $snap"); next }
64
 
65
    my $retention_seconds = $days * 86400;
66
    my $age = $now - $snap_epoch;
67
    if ($age > $retention_seconds) {
68
        push @candidates, $snap;
69
    } else {
70
        logmsg("keep: $snap (age " . int($age/86400) . "d <= ${days}d)");
71
    }
72
}
73
close $fh;
74
 
75
if (!@candidates) {
76
    print "No snapshots to remove.\n";
77
    exit 0;
78
}
79
 
80
printf "Snapshots to remove (%d):\n", scalar @candidates;
81
for my $s (@candidates) { printf "  %s\n", $s }
82
 
83
if ($DRY_RUN) {
84
    print "\nDry-run: no snapshots were destroyed. Use -f to actually remove them.\n";
85
    exit 0;
86
}
87
 
88
# actual removal
89
my $failed = 0;
90
for my $s (@candidates) {
91
    printf "Destroying: %s\n", $s;
92
    my $rc = system($ZFS_CMD, 'destroy', $s);
93
    if ($rc != 0) {
94
        printf STDERR "Failed to destroy %s\n", $s;
95
        $failed = 1;
96
    }
97
}
98
 
99
if ($failed) {
100
    print STDERR "One or more destroys failed.\n";
101
    exit 1;
102
}
103
 
104
print "Done.\n";