package metadata import ( "context" "fmt" "strings" "github.com/boltdb/bolt" "github.com/containerd/containerd/gc" "github.com/containerd/containerd/log" "github.com/pkg/errors" ) const ( // ResourceUnknown specifies an unknown resource ResourceUnknown gc.ResourceType = iota // ResourceContent specifies a content resource ResourceContent // ResourceSnapshot specifies a snapshot resource ResourceSnapshot // ResourceContainer specifies a container resource ResourceContainer // ResourceTask specifies a task resource ResourceTask ) var ( labelGCRoot = []byte("containerd.io/gc.root") labelGCSnapRef = []byte("containerd.io/gc.ref.snapshot.") labelGCContentRef = []byte("containerd.io/gc.ref.content") ) func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { v1bkt := tx.Bucket(bucketKeyVersion) if v1bkt == nil { return nil } // iterate through each namespace v1c := v1bkt.Cursor() for k, v := v1c.First(); k != nil; k, v = v1c.Next() { if v != nil { continue } nbkt := v1bkt.Bucket(k) ns := string(k) lbkt := nbkt.Bucket(bucketKeyObjectLeases) if lbkt != nil { if err := lbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } libkt := lbkt.Bucket(k) cbkt := libkt.Bucket(bucketKeyObjectContent) if cbkt != nil { if err := cbkt.ForEach(func(k, v []byte) error { select { case nc <- gcnode(ResourceContent, ns, string(k)): case <-ctx.Done(): return ctx.Err() } return nil }); err != nil { return err } } sbkt := libkt.Bucket(bucketKeyObjectSnapshots) if sbkt != nil { if err := sbkt.ForEach(func(sk, sv []byte) error { if sv != nil { return nil } snbkt := sbkt.Bucket(sk) return snbkt.ForEach(func(k, v []byte) error { select { case nc <- gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)): case <-ctx.Done(): return ctx.Err() } return nil }) }); err != nil { return err } } return nil }); err != nil { return err } } ibkt := nbkt.Bucket(bucketKeyObjectImages) if ibkt != nil { if err := ibkt.ForEach(func(k, v []byte) error { if v != nil { return nil } target := ibkt.Bucket(k).Bucket(bucketKeyTarget) if target != nil { contentKey := string(target.Get(bucketKeyDigest)) select { case nc <- gcnode(ResourceContent, ns, contentKey): case <-ctx.Done(): return ctx.Err() } } return sendSnapshotRefs(ns, ibkt.Bucket(k), func(n gc.Node) { select { case nc <- n: case <-ctx.Done(): } }) }); err != nil { return err } } cbkt := nbkt.Bucket(bucketKeyObjectContent) if cbkt != nil { cbkt = cbkt.Bucket(bucketKeyObjectBlob) } if cbkt != nil { if err := cbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } return sendRootRef(ctx, nc, gcnode(ResourceContent, ns, string(k)), cbkt.Bucket(k)) }); err != nil { return err } } cbkt = nbkt.Bucket(bucketKeyObjectContainers) if cbkt != nil { if err := cbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } snapshotter := string(cbkt.Bucket(k).Get(bucketKeySnapshotter)) if snapshotter != "" { ss := string(cbkt.Bucket(k).Get(bucketKeySnapshotKey)) select { case nc <- gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, ss)): case <-ctx.Done(): return ctx.Err() } } // TODO: Send additional snapshot refs through labels return sendSnapshotRefs(ns, cbkt.Bucket(k), func(n gc.Node) { select { case nc <- n: case <-ctx.Done(): } }) }); err != nil { return err } } sbkt := nbkt.Bucket(bucketKeyObjectSnapshots) if sbkt != nil { if err := sbkt.ForEach(func(sk, sv []byte) error { if sv != nil { return nil } snbkt := sbkt.Bucket(sk) return snbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } return sendRootRef(ctx, nc, gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)), snbkt.Bucket(k)) }) }); err != nil { return err } } } return nil } func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)) error { if node.Type == ResourceContent { bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(node.Key)) if bkt == nil { // Node may be created from dead edge return nil } if err := sendSnapshotRefs(node.Namespace, bkt, fn); err != nil { return err } return sendContentRefs(node.Namespace, bkt, fn) } else if node.Type == ResourceSnapshot { parts := strings.SplitN(node.Key, "/", 2) if len(parts) != 2 { return errors.Errorf("invalid snapshot gc key %s", node.Key) } ss := parts[0] name := parts[1] bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectSnapshots, []byte(ss), []byte(name)) if bkt == nil { getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectSnapshots).ForEach(func(k, v []byte) error { return nil }) // Node may be created from dead edge return nil } if pv := bkt.Get(bucketKeyParent); len(pv) > 0 { fn(gcnode(ResourceSnapshot, node.Namespace, fmt.Sprintf("%s/%s", ss, pv))) } return sendSnapshotRefs(node.Namespace, bkt, fn) } return nil } func scanAll(ctx context.Context, tx *bolt.Tx, fn func(ctx context.Context, n gc.Node) error) error { v1bkt := tx.Bucket(bucketKeyVersion) if v1bkt == nil { return nil } // iterate through each namespace v1c := v1bkt.Cursor() for k, v := v1c.First(); k != nil; k, v = v1c.Next() { if v != nil { continue } nbkt := v1bkt.Bucket(k) ns := string(k) sbkt := nbkt.Bucket(bucketKeyObjectSnapshots) if sbkt != nil { if err := sbkt.ForEach(func(sk, sv []byte) error { if sv != nil { return nil } snbkt := sbkt.Bucket(sk) return snbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } node := gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)) return fn(ctx, node) }) }); err != nil { return err } } cbkt := nbkt.Bucket(bucketKeyObjectContent) if cbkt != nil { cbkt = cbkt.Bucket(bucketKeyObjectBlob) } if cbkt != nil { if err := cbkt.ForEach(func(k, v []byte) error { if v != nil { return nil } node := gcnode(ResourceContent, ns, string(k)) return fn(ctx, node) }); err != nil { return err } } } return nil } func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error { v1bkt := tx.Bucket(bucketKeyVersion) if v1bkt == nil { return nil } nsbkt := v1bkt.Bucket([]byte(node.Namespace)) if nsbkt == nil { return nil } switch node.Type { case ResourceContent: cbkt := nsbkt.Bucket(bucketKeyObjectContent) if cbkt != nil { cbkt = cbkt.Bucket(bucketKeyObjectBlob) } if cbkt != nil { log.G(ctx).WithField("key", node.Key).Debug("remove content") return cbkt.DeleteBucket([]byte(node.Key)) } case ResourceSnapshot: sbkt := nsbkt.Bucket(bucketKeyObjectSnapshots) if sbkt != nil { parts := strings.SplitN(node.Key, "/", 2) if len(parts) != 2 { return errors.Errorf("invalid snapshot gc key %s", node.Key) } ssbkt := sbkt.Bucket([]byte(parts[0])) if ssbkt != nil { log.G(ctx).WithField("key", parts[1]).WithField("snapshotter", parts[0]).Debug("remove snapshot") return ssbkt.DeleteBucket([]byte(parts[1])) } } } return nil } // sendSnapshotRefs sends all snapshot references referred to by the labels in the bkt func sendSnapshotRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error { lbkt := bkt.Bucket(bucketKeyObjectLabels) if lbkt != nil { lc := lbkt.Cursor() for k, v := lc.Seek(labelGCSnapRef); k != nil && strings.HasPrefix(string(k), string(labelGCSnapRef)); k, v = lc.Next() { snapshotter := string(k[len(labelGCSnapRef):]) fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v))) } } return nil } // sendContentRefs sends all content references referred to by the labels in the bkt func sendContentRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error { lbkt := bkt.Bucket(bucketKeyObjectLabels) if lbkt != nil { lc := lbkt.Cursor() labelRef := string(labelGCContentRef) for k, v := lc.Seek(labelGCContentRef); k != nil && strings.HasPrefix(string(k), labelRef); k, v = lc.Next() { if ks := string(k); ks != labelRef { // Allow reference naming, ignore names if ks[len(labelRef)] != '.' { continue } } fn(gcnode(ResourceContent, ns, string(v))) } } return nil } func isRootRef(bkt *bolt.Bucket) bool { lbkt := bkt.Bucket(bucketKeyObjectLabels) if lbkt != nil { rv := lbkt.Get(labelGCRoot) if rv != nil { // TODO: interpret rv as a timestamp and skip if expired return true } } return false } func sendRootRef(ctx context.Context, nc chan<- gc.Node, n gc.Node, bkt *bolt.Bucket) error { if isRootRef(bkt) { select { case nc <- n: case <-ctx.Done(): return ctx.Err() } } return nil } func gcnode(t gc.ResourceType, ns, key string) gc.Node { return gc.Node{ Type: t, Namespace: ns, Key: key, } }