From 27fc1794907c95570cd0c8d3d6cd11301d5e5a53 Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Thu, 5 Jun 2025 21:59:30 +0200 Subject: [PATCH] implemented adding of taken snapshot to the cache file and a new parameter for setting an custom cache expire time --- README.md | 4 +++ sanoid | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2ce10c..fadffb4 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,10 @@ For more full details on sanoid.conf settings see [Wiki page](https://github.com This clears out sanoid's zfs snapshot listing cache. This is normally not needed. ++ --cache-ttl=SECONDS + + Set custom cache expire time in seconds (default: 20 minutes). + + --version This prints the version number, and exits. diff --git a/sanoid b/sanoid index c3242a1..57a942f 100755 --- a/sanoid +++ b/sanoid @@ -12,6 +12,7 @@ use warnings; use Config::IniFiles; # read samba-style conf file use Data::Dumper; # debugging - print contents of hash use File::Path 'make_path'; +use File::Copy; use Getopt::Long qw(:config auto_version auto_help); use Pod::Usage; # pod2usage use Time::Local; # to parse dates in reverse @@ -26,7 +27,7 @@ GetOptions(\%args, "verbose", "debug", "cron", "readonly", "quiet", "configdir=s", "cache-dir=s", "run-dir=s", "monitor-health", "force-update", "monitor-snapshots", "take-snapshots", "prune-snapshots", "force-prune", - "monitor-capacity" + "monitor-capacity", "cache-ttl=i" ) or pod2usage(2); # If only config directory (or nothing) has been specified, default to --cron --verbose @@ -54,6 +55,13 @@ make_path($run_dir); my $cacheTTL = 1200; # 20 minutes +if ($args{'cache-ttl'}) { + if ($args{'cache-ttl'} < 0) { + die "ERROR: cache-ttl needs to be positive!\n"; + } + $cacheTTL = $args{'cache-ttl'}; +} + # Allow a much older snapshot cache file than default if _only_ "--monitor-*" action commands are given # (ignore "--verbose", "--configdir" etc) if ( @@ -67,6 +75,7 @@ if ( || $args{'take-snapshots'} || $args{'prune-snapshots'} || $args{'force-prune'} + || $args{'cache-ttl'} ) ) { # The command combination above must not assert true for any command that takes or prunes snapshots @@ -86,6 +95,7 @@ my %config = init($conf_file,$default_conf_file); my %pruned; my %capacitycache; +my %taken; my %snaps; my %snapsbytype; @@ -592,6 +602,7 @@ sub take_snapshots { } if (%newsnapsgroup) { + $forcecacheupdate = 0; while ((my $path, my $snapData) = each(%newsnapsgroup)) { my $recursiveFlag = $snapData->{recursive}; my $dstHandling = $snapData->{handleDst}; @@ -662,9 +673,17 @@ sub take_snapshots { } }; + if ($exit == 0) { + $taken{$snap} = { + 'time' => time(), + 'recursive' => $recursiveFlag + }; + } + $exit == 0 or do { if ($dstHandling) { if ($stderr =~ /already exists/) { + $forcecacheupdate = 1; $exit = 0; $snap =~ s/_([a-z]+)$/dst_$1/g; if ($args{'verbose'}) { print "taking dst snapshot $snap$extraMessage\n"; } @@ -714,8 +733,8 @@ sub take_snapshots { } } } - $forcecacheupdate = 1; - %snaps = getsnaps(%config,$cacheTTL,$forcecacheupdate); + addcachedsnapshots(); + %snaps = getsnaps(\%config,$cacheTTL,$forcecacheupdate); } } @@ -1740,6 +1759,11 @@ sub removecachedsnapshots { print FH $snapline unless ( exists($pruned{$snap}) ); } close FH; + + # preserve mtime of cache for expire check + my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($cache); + utime($atime, $mtime, "$cache.tmp"); + rename("$cache.tmp", "$cache") or die "Could not rename to $cache!\n"; removelock('sanoid_cacheupdate'); @@ -1753,6 +1777,61 @@ sub removecachedsnapshots { #######################################################################################################################3 #######################################################################################################################3 +sub addcachedsnapshots { + if (not %taken) { + return; + } + + my $unlocked = checklock('sanoid_cacheupdate'); + + # wait until we can get a lock to do our cache changes + while (not $unlocked) { + if ($args{'verbose'}) { print "INFO: waiting for cache update lock held by another sanoid process.\n"; } + sleep(10); + $unlocked = checklock('sanoid_cacheupdate'); + } + + writelock('sanoid_cacheupdate'); + + if ($args{'verbose'}) { + print "INFO: adding taken snapshots to cache.\n"; + } + + copy($cache, "$cache.tmp") or die "Could not copy to $cache.tmp!\n"; + + open FH, ">> $cache.tmp" or die "Could not write to $cache.tmp!\n"; + while((my $snap, my $details) = each(%taken)) { + my @parts = split("@", $snap, 2); + + my $suffix = $parts[1] . "\tcreation\t" . $details->{time} . "\t-"; + my $dataset = $parts[0]; + + print FH "${dataset}\@${suffix}\n"; + + if ($details->{recursive}) { + my @datasets = getchilddatasets($dataset); + + foreach my $dataset(@datasets) { + print FH "${dataset}\@${suffix}\n"; + } + } + } + + close FH; + + # preserve mtime of cache for expire check + my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($cache); + utime($atime, $mtime, "$cache.tmp"); + + rename("$cache.tmp", "$cache") or die "Could not rename to $cache!\n"; + + removelock('sanoid_cacheupdate'); +} + +#######################################################################################################################3 +#######################################################################################################################3 +#######################################################################################################################3 + sub runscript { my $key=shift; my $dataset=shift; @@ -1851,6 +1930,7 @@ Options: --take-snapshots Creates snapshots as specified in sanoid.conf --prune-snapshots Purges expired snapshots as specified in sanoid.conf --force-prune Purges expired snapshots even if a send/recv is in progress + --cache-ttl=SECONDS Set custom cache expire time in seconds (default: 20 minutes) --help Prints this helptext --version Prints the version number