@@ -10,6 +10,7 @@ import (
1010"strconv"
1111"strings"
1212"time"
13+ "unicode"
1314
1415"github.com/pkg/errors"
1516
@@ -305,12 +306,21 @@ func (m *Manager) DestroySnapshot(snapshotName string) error {
305306return nil
306307}
307308
308- // CleanupSnapshots destroys old snapshots considering retention limit.
309+ // CleanupSnapshots destroys old snapshots considering retention limit and related clones .
309310func (m * Manager )CleanupSnapshots (retentionLimit int ) ([]string ,error ) {
311+ clonesCmd := fmt .Sprintf ("zfs list -S clones -o name,origin -H -r %s" ,m .config .Pool .Name )
312+
313+ clonesOutput ,err := m .runner .Run (clonesCmd )
314+ if err != nil {
315+ return nil ,errors .Wrap (err ,"failed to list snapshots" )
316+ }
317+
318+ busySnapshots := m .getBusySnapshotList (clonesOutput )
319+
310320cleanupCmd := fmt .Sprintf (
311- "zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | head -n -%d " +
321+ "zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | head -n -%d%s " +
312322"| xargs -n1 --no-run-if-empty zfs destroy -R " ,
313- dataStateAtLabel ,m .config .Pool .Name ,retentionLimit )
323+ dataStateAtLabel ,m .config .Pool .Name ,retentionLimit , excludeBusySnapshots ( busySnapshots ) )
314324
315325out ,err := m .runner .Run (cleanupCmd )
316326if err != nil {
@@ -322,6 +332,52 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
322332return lines ,nil
323333}
324334
335+ func (m * Manager )getBusySnapshotList (clonesOutput string ) []string {
336+ systemClones ,userClones := make (map [string ]string ),make (map [string ]struct {})
337+
338+ userClonePrefix := m .config .Pool .Name + "/" + util .ClonePrefix
339+
340+ for _ ,line := range strings .Split (clonesOutput ,"\n " ) {
341+ cloneLine := strings .FieldsFunc (line ,unicode .IsSpace )
342+
343+ if len (cloneLine )!= 2 || cloneLine [1 ]== "-" {
344+ continue
345+ }
346+
347+ if strings .HasPrefix (cloneLine [0 ],userClonePrefix ) {
348+ origin := cloneLine [1 ]
349+
350+ if idx := strings .Index (origin ,"@" );idx != - 1 {
351+ origin = origin [:idx ]
352+ }
353+
354+ userClones [origin ]= struct {}{}
355+
356+ continue
357+ }
358+
359+ systemClones [cloneLine [0 ]]= cloneLine [1 ]
360+ }
361+
362+ busySnapshots := make ([]string ,0 ,len (userClones ))
363+
364+ for userClone := range userClones {
365+ busySnapshots = append (busySnapshots ,systemClones [userClone ])
366+ }
367+
368+ return busySnapshots
369+ }
370+
371+ // excludeBusySnapshots excludes snapshots that match a pattern by name.
372+ // The exclusion logic relies on the fact that snapshots have unique substrings (timestamps).
373+ func excludeBusySnapshots (busySnapshots []string )string {
374+ if len (busySnapshots )== 0 {
375+ return ""
376+ }
377+
378+ return fmt .Sprintf ("| grep -Ev '%s' " ,strings .Join (busySnapshots ,"|" ))
379+ }
380+
325381// GetSessionState returns a state of a session.
326382func (m * Manager )GetSessionState (name string ) (* resources.SessionState ,error ) {
327383entries ,err := m .listFilesystems (m .config .Pool .Name )