Skip to content

Instantly share code, notes, and snippets.

@nakal
Last active July 30, 2016 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nakal/e3b2f36b6e6a32ae7b67d8aa52445516 to your computer and use it in GitHub Desktop.
Save nakal/e3b2f36b6e6a32ae7b67d8aa52445516 to your computer and use it in GitHub Desktop.
#!/usr/bin/env perl
use strict;
use warnings;
my $verbose = 0;
my ($senddiff) = @ARGV;
if (defined($senddiff)) {
$senddiff = 1;
}
# keep snapshots per ZFS dataset
my $snap_poolname = "pool";
my $snap_fs_home_keep = 30;
my %snapfs = (
"$snap_poolname/ROOT/default" => $snap_fs_home_keep,
"$snap_poolname/ROOT/safe" => $snap_fs_home_keep,
"$snap_poolname/var/tmp" => $snap_fs_home_keep,
"$snap_poolname/var/log" => $snap_fs_home_keep,
"$snap_poolname/var/mail" => $snap_fs_home_keep
);
# skip reports for these ZFS datasets
my %nodiffs = (
"$snap_poolname/usr/home" => 1,
"$snap_poolname/var/tmp" => 1,
"$snap_poolname/var/log" => 1,
"$snap_poolname/var/mail" => 1
);
# add user directories
if (open (my $FH, "-|", "/sbin/zfs list -H -o name -r $snap_poolname/usr/home")) {
_log(sprintf("[$snap_poolname/usr/home] Determining user ZFS datasets\n"));
while (<$FH>) {
chomp;
my $fs = $_;
$snapfs{$fs} = $snap_fs_home_keep if (!defined($snapfs{$fs}));
}
close($FH);
}
my $snap_regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}:[0-9]{2}:[0-9]{2}";
foreach (keys %snapfs) {
my $fs = $_;
if (!&ismounted($fs)) {
_log(sprintf("[%s] Is not mounted, skip.\n", $fs));
next;
}
my $keep = $snapfs{$fs};
if ($keep == 0) {
_log(sprintf("[%s] Is excluded, skip.\n", $fs));
next;
}
_log(sprintf("[%s] Snapshotting ...\n", $fs));
my $snapname = `/bin/date +%Y-%m-%d_%H:%M:%S`;
chomp($snapname);
my $snapyest = "";
if ($snapname =~ m/$snap_regex/) {
_log(sprintf("\t -> %s@%s\n", $fs, $snapname));
system("/sbin/zfs snapshot $fs" . "@" . "$snapname");
my @snapshots = &list_snapshots($fs);
my $snapnum = scalar @snapshots;
_log(sprintf("[%s] Have %u snapshot(s) ...\n", $fs, $snapnum));
@snapshots = reverse @snapshots;
my $snapsec_now = &ts_parse($snapname);
foreach my $snapstr (@snapshots) {
my $snap_sec = &ts_parse($snapstr);
if ($snapsec_now - $snap_sec > 23 * 60 * 60) {
$snapyest = $snapstr;
last;
}
}
if (!defined($nodiffs{$fs}) and $senddiff and length($snapyest) > 0) {
my $content = "";
_log(sprintf("[%s] Will diff %s and %s...\n", $fs, $snapname, $snapyest));
if (open (my $FH, "-|", "/sbin/zfs diff $fs\@". $snapyest . " $fs\@" . $snapname)) {
_log(sprintf("[%s] Calculating diff between %s and %s...\n", $fs, $snapname, $snapyest));
while (<$FH>) {
my $line = $_;
my $addline = 1;
$addline = 0 if (m/\<xattrdir\>/);
$addline = 0 if (m/^M.*\/Thumbs.db$/);
$content .= $line if ($addline == 1);
}
close($FH);
} else {
_log(sprintf("[%s] Diff failed for %s and %s...\n", $fs, $snapname, $snapyest));
}
if (length($content) > 0) {
# and open (my $MH, "|-", "/usr/bin/mail -s \'Latest modifications on $fs\' root")) {
#print $MH $content;
#close($MH);
printf("=== [%s] lastest changes =======================\n", $fs);
print $content . "\n";
}
}
if ($snapnum > $keep) {
_log(sprintf("[%s] Tidying up old snapshots (keeping %u) ...\n", $fs, $keep));
my @snaps_to_delete = (@snapshots)[$keep..$#snapshots];
foreach (@snaps_to_delete) {
my $dsnap = $_;
_log(sprintf("[%s] Deleting snapshot %s ...\n", $fs, $dsnap));
system("/sbin/zfs destroy $fs" . "@" . "$dsnap");
}
}
} else {
die("Auto-created snapshot name $snapname does not match regexp!");
}
}
exit 0;
sub ismounted {
my ($fs) = @_;
my $str = `/sbin/zfs get -Ho value mounted $fs`;
chomp $str;
return $str eq 'yes' ? 1 : 0;
}
sub list_snapshots {
my ($fs) = @_;
#printf("Listing snapshots under %s...\n", $fs);
my @out = `/sbin/zfs list -H -t snapshot -r $fs`;
my @ret = ();
foreach (@out) {
my $ln = $_;
if ($ln =~ m/^$fs@($snap_regex)\t/) {
my $snapname = $1;
#printf("Snapname: %s\n", $snapname);
push @ret, $snapname;
}
}
return sort @ret;
}
sub _log {
my ($msg) = @_;
print $msg if ($verbose);
}
sub ts_parse {
my ($ts) = @_;
#print "Parsing '$ts'\n";
my @secs = `/bin/date -j -f '%Y-%m-%d_%H:%M:%S' $ts +%s`;
return $secs[0];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment