@@ -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 {
305306 return 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+
310320 cleanupCmd := 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
315325 out , err := m .runner .Run (cleanupCmd )
316326 if err != nil {
@@ -322,6 +332,52 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
322332 return 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 ) {
327383 entries , err := m .listFilesystems (m .config .Pool .Name )
0 commit comments