2016-06-13 17:42:17 +02:00
|
|
|
#!/usr/bin/perl
|
2015-04-07 00:10:02 +02:00
|
|
|
|
2017-11-08 17:30:52 +01:00
|
|
|
# this software is licensed for use under the Free Software Foundation's GPL v3.0 license, as retrieved
|
2015-04-07 00:10:02 +02:00
|
|
|
# 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.
|
|
|
|
|
2019-11-05 17:36:36 +01:00
|
|
|
$::VERSION = '2.0.2';
|
2015-04-07 00:10:02 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
2019-11-05 17:36:36 +01:00
|
|
|
use Getopt::Long qw(:config auto_version auto_help);
|
|
|
|
use Pod::Usage;
|
2015-04-07 00:10:02 +02:00
|
|
|
|
2019-11-05 17:25:38 +01:00
|
|
|
my $zfs = 'zfs';
|
2019-11-05 17:36:36 +01:00
|
|
|
my %args = ('path' => '');
|
|
|
|
GetOptions(\%args, "path=s") or pod2usage(2);
|
|
|
|
|
|
|
|
if ($args{'path'} eq '') {
|
|
|
|
if (scalar(@ARGV) < 1) {
|
|
|
|
warn "file path missing!\n";
|
|
|
|
pod2usage(2);
|
|
|
|
exit 127;
|
|
|
|
} else {
|
|
|
|
$args{'path'} = $ARGV[0];
|
|
|
|
}
|
|
|
|
}
|
2015-04-07 00:10:02 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-11-05 17:30:43 +01:00
|
|
|
if (!defined $size) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2015-04-07 00:10:02 +02:00
|
|
|
# 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;
|
|
|
|
}
|
|
|
|
}
|
2017-11-08 17:30:52 +01:00
|
|
|
if (! $duplicate) {
|
2015-04-07 00:10:02 +02:00
|
|
|
$versions{$filename}{'size'} = $size;
|
|
|
|
$versions{$filename}{'mtime'} = $mtime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-05 17:33:43 +01:00
|
|
|
my $filename = "$dataset/$relpath";
|
|
|
|
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
|
|
|
|
|
|
|
|
if (defined $size) {
|
|
|
|
$versions{$filename}{'size'} = $size;
|
|
|
|
$versions{$filename}{'mtime'} = $mtime;
|
|
|
|
}
|
|
|
|
|
2017-11-08 17:30:52 +01:00
|
|
|
return %versions;
|
2015-04-07 00:10:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2017-11-08 17:30:52 +01:00
|
|
|
|
2015-04-07 00:10:02 +02:00
|
|
|
my ($path) = @_;
|
|
|
|
|
2019-11-05 17:19:56 +01:00
|
|
|
open FH, "$zfs list -H -t filesystem -o mountpoint,mounted |";
|
2015-04-07 00:10:02 +02:00
|
|
|
my @datasets = <FH>;
|
|
|
|
close FH;
|
2017-11-08 17:30:52 +01:00
|
|
|
|
2015-04-07 00:10:02 +02:00
|
|
|
my @matchingdatasets;
|
|
|
|
foreach my $dataset (@datasets) {
|
|
|
|
chomp $dataset;
|
2019-11-05 17:19:56 +01:00
|
|
|
my ($mountpoint, $mounted) = ($dataset =~ m/([^\t]*)\t*(.*)/);
|
|
|
|
if ($mounted ne "yes") {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $path =~ /^$mountpoint/ ) { push @matchingdatasets, $mountpoint; }
|
2015-04-07 00:10:02 +02:00
|
|
|
}
|
2017-11-08 17:30:52 +01:00
|
|
|
|
2015-04-07 00:10:02 +02:00
|
|
|
my $bestmatch = '';
|
|
|
|
foreach my $dataset (@matchingdatasets) {
|
|
|
|
if ( length $dataset > length $bestmatch ) { $bestmatch = $dataset; }
|
|
|
|
}
|
|
|
|
return $bestmatch;
|
|
|
|
}
|
|
|
|
|
2019-11-05 17:36:36 +01:00
|
|
|
__END__
|
2015-04-07 00:10:02 +02:00
|
|
|
|
2019-11-05 17:36:36 +01:00
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
findoid - ZFS file version listing tool
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
findoid [options] FILE
|
|
|
|
|
|
|
|
FILE local path to file for version listing
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
|
|
|
--path=FILE alternative to specify file path to list versions for
|
|
|
|
|
|
|
|
--help Prints this helptext
|
|
|
|
--version Prints the version number
|