+sub prune_backups {
+ my ($cfg, $storeid, $keep, $vmid, $type, $dryrun, $logfunc) = @_;
+
+ my $scfg = storage_config($cfg, $storeid);
+ die "storage '$storeid' does not support backups\n" if !$scfg->{content}->{backup};
+
+ if (!defined($keep)) {
+ die "no prune-backups options configured for storage '$storeid'\n"
+ if !defined($scfg->{'prune-backups'});
+ $keep = PVE::JSONSchema::parse_property_string('prune-backups', $scfg->{'prune-backups'});
+ }
+
+ activate_storage($cfg, $storeid);
+
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->prune_backups($scfg, $storeid, $keep, $vmid, $type, $dryrun, $logfunc);
+}
+
+my $prune_mark = sub {
+ my ($prune_entries, $keep_count, $id_func) = @_;
+
+ return if !$keep_count;
+
+ my $already_included = {};
+ my $newly_included = {};
+
+ foreach my $prune_entry (@{$prune_entries}) {
+ my $mark = $prune_entry->{mark};
+ my $id = $id_func->($prune_entry->{ctime});
+ $already_included->{$id} = 1 if defined($mark) && $mark eq 'keep';
+ }
+
+ foreach my $prune_entry (@{$prune_entries}) {
+ my $mark = $prune_entry->{mark};
+ my $id = $id_func->($prune_entry->{ctime});
+
+ next if defined($mark) || $already_included->{$id};
+
+ if (!$newly_included->{$id}) {
+ last if scalar(keys %{$newly_included}) >= $keep_count;
+ $newly_included->{$id} = 1;
+ $prune_entry->{mark} = 'keep';
+ } else {
+ $prune_entry->{mark} = 'remove';
+ }
+ }
+};
+
+sub prune_mark_backup_group {
+ my ($backup_group, $keep) = @_;
+
+ my $keep_all = delete $keep->{'keep-all'};
+
+ if ($keep_all || !scalar(grep {$_ > 0} values %{$keep})) {
+ $keep = { 'keep-all' => 1 } if $keep_all;
+ foreach my $prune_entry (@{$backup_group}) {
+ $prune_entry->{mark} = 'keep';
+ }
+ return;
+ }
+
+ my $prune_list = [ sort { $b->{ctime} <=> $a->{ctime} } @{$backup_group} ];
+
+ $prune_mark->($prune_list, $keep->{'keep-last'}, sub {
+ my ($ctime) = @_;
+ return $ctime;
+ });
+ $prune_mark->($prune_list, $keep->{'keep-hourly'}, sub {
+ my ($ctime) = @_;
+ my (undef, undef, $hour, $day, $month, $year) = localtime($ctime);
+ return "$hour/$day/$month/$year";
+ });
+ $prune_mark->($prune_list, $keep->{'keep-daily'}, sub {
+ my ($ctime) = @_;
+ my (undef, undef, undef, $day, $month, $year) = localtime($ctime);
+ return "$day/$month/$year";
+ });
+ $prune_mark->($prune_list, $keep->{'keep-weekly'}, sub {
+ my ($ctime) = @_;
+ my ($sec, $min, $hour, $day, $month, $year) = localtime($ctime);
+ my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month, $year));
+ my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $year));
+ return "$iso_week/$iso_week_year";
+ });
+ $prune_mark->($prune_list, $keep->{'keep-monthly'}, sub {
+ my ($ctime) = @_;
+ my (undef, undef, undef, undef, $month, $year) = localtime($ctime);
+ return "$month/$year";
+ });
+ $prune_mark->($prune_list, $keep->{'keep-yearly'}, sub {
+ my ($ctime) = @_;
+ my $year = (localtime($ctime))[5];
+ return "$year";
+ });
+
+ foreach my $prune_entry (@{$prune_list}) {
+ $prune_entry->{mark} //= 'remove';
+ }
+}
+