From c0f1131fa6fcc97698d3f903521e04e27d0526b7 Mon Sep 17 00:00:00 2001 From: Jim Salter Date: Mon, 6 Apr 2015 18:10:02 -0400 Subject: [PATCH] added findoid tool --- CHANGELIST | 2 + VERSION | 2 +- findoid | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++ sanoid | 2 +- syncoid | 2 +- 5 files changed, 185 insertions(+), 3 deletions(-) create mode 100755 findoid diff --git a/CHANGELIST b/CHANGELIST index d2c4739..4e82f21 100644 --- a/CHANGELIST +++ b/CHANGELIST @@ -1,3 +1,5 @@ +1.4.0 added findoid tool - find and list all versions of a given file in all available ZFS snapshots. use: findoid /path/to/file + 1.3.1 whoops - prevent process_children_only from getting set from blank value in defaults 1.3.0 changed monitor_children_only to process_children_only. which keeps sanoid from messing around with empty parent datasets at all. diff --git a/VERSION b/VERSION index 3a3cd8c..88c5fb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.1 +1.4.0 diff --git a/findoid b/findoid new file mode 100755 index 0000000..701a597 --- /dev/null +++ b/findoid @@ -0,0 +1,180 @@ +#!/usr/bin/perl + +# this software is licensed for use under the Free Software Foundation's GPL v3.0 license, as retrieved +# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this +# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE. + + +use strict; +use warnings; + +my $zfs = '/sbin/zfs'; +my %args = getargs(@ARGV); + +my $progversion = '1.4.0'; + +if ($args{'version'}) { print "$progversion\n"; exit 0; } + +my $dataset = getdataset($args{'path'}); + +my %versions = getversions($args{'path'}, $dataset); + +foreach my $version (sort { $versions{$a}{'mtime'} <=> $versions{$b}{'mtime'} } keys %versions) { + my $disptime = localtime($versions{$version}{'mtime'}); + my $dispsize = humansize($versions{$version}{'size'}); + + print "$disptime\t$dispsize\t$version\n"; +} + +exit 0; + +################################################################### +################################################################### +################################################################### + +sub humansize { + + my ($rawsize) = @_; + my $humansize; + + if ($rawsize > 1024*1024*1024) { + $humansize = sprintf("%.1f",$rawsize/1024/1024/1024) . ' GB'; + } elsif ($rawsize > 1024*1024) { + $humansize = sprintf("%.1f",$rawsize/1024/1024) . ' MB'; + } elsif ($rawsize > 255) { + $humansize = sprintf("%.1f",$rawsize/1024) . ' KB'; + } else { + $humansize = $rawsize . ' Bytes'; + } + + return $humansize; +} + +sub getversions { + my ($path, $dataset) = @_; + my @snaps = findsnaps($dataset, $args{'path'}); + + my $snappath = '.zfs/snapshot'; + my $relpath = $path; + $relpath =~ s/^$dataset\///; + + my %versions; + + foreach my $snap (@snaps) { + my $filename = "$dataset/$snappath/$snap/$relpath"; + my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); + + # only push to the $versions hash if this size and mtime aren't already present (simple dedupe) + my $duplicate = 0; + foreach my $version (keys %versions) { + if ($versions{$version}{'size'} eq $size && $versions{$version}{'mtime'} eq $mtime) { + $duplicate = 1; + } + } + if (! $duplicate) { + $versions{$filename}{'size'} = $size; + $versions{$filename}{'mtime'} = $mtime; + } + } + + return %versions; +} + +sub findsnaps { + my ($dataset, $path) = @_; + + my $snappath = '.zfs/snapshot'; + + my $relpath = $path; + $relpath =~ s/^$dataset//; + + my @snaps; + opendir (my $dh, "$dataset/$snappath"); + while (my $dir=(readdir $dh)) { + if ($dir ne '.' && $dir ne '..') { push @snaps, $dir; } + } + closedir $dh; + + return @snaps; +} + +sub getdataset { + + my ($path) = @_; + + open FH, "$zfs list -Ho mountpoint |"; + my @datasets = ; + close FH; + + my @matchingdatasets; + foreach my $dataset (@datasets) { + chomp $dataset; + if ( $path =~ /^$dataset/ ) { push @matchingdatasets, $dataset; } + } + + my $bestmatch = ''; + foreach my $dataset (@matchingdatasets) { + if ( length $dataset > length $bestmatch ) { $bestmatch = $dataset; } + } + return $bestmatch; +} + +sub getargs { + my @args = @_; + my %args; + + my %novaluearg; + my %validarg; + push my @validargs, ('debug','version'); + foreach my $item (@validargs) { $validarg{$item} = 1; } + push my @novalueargs, ('debug','version'); + foreach my $item (@novalueargs) { $novaluearg{$item} = 1; } + + while (my $rawarg = shift(@args)) { + my $arg = $rawarg; + my $argvalue; + if ($rawarg =~ /=/) { + # user specified the value for a CLI argument with = + # instead of with blank space. separate appropriately. + $argvalue = $arg; + $arg =~ s/=.*$//; + $argvalue =~ s/^.*=//; + } + if ($rawarg =~ /^--/) { + # doubledash arg + $arg =~ s/^--//; + if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; } + if ($novaluearg{$arg}) { + $args{$arg} = 1; + } else { + # if this CLI arg takes a user-specified value and + # we don't already have it, then the user must have + # specified with a space, so pull in the next value + # from the array as this value rather than as the + # next argument. + if ($argvalue eq '') { $argvalue = shift(@args); } + $args{$arg} = $argvalue; + } + } elsif ($arg =~ /^-/) { + # singledash arg + $arg =~ s/^-//; + if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; } + if ($novaluearg{$arg}) { + $args{$arg} = 1; + } else { + # if this CLI arg takes a user-specified value and + # we don't already have it, then the user must have + # specified with a space, so pull in the next value + # from the array as this value rather than as the + # next argument. + if ($argvalue eq '') { $argvalue = shift(@args); } + $args{$arg} = $argvalue; + } + } else { + # bare arg + $args{'path'} = $arg; + } + } + + return %args; +} diff --git a/sanoid b/sanoid index b7345e9..0b92140 100755 --- a/sanoid +++ b/sanoid @@ -4,7 +4,7 @@ # from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this # project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE. -my $version = '1.3.1'; +my $version = '1.4.0'; use strict; use Config::IniFiles; # read samba-style conf file diff --git a/syncoid b/syncoid index 4aad399..c438bea 100755 --- a/syncoid +++ b/syncoid @@ -4,7 +4,7 @@ # from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this # project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE. -my $version = '1.3.1'; +my $version = '1.4.0'; use strict; use Data::Dumper;