github.com/demonoid81/containerd@v1.3.4/metadata/content.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package metadata
    18  
    19  import (
    20  	"context"
    21  	"encoding/binary"
    22  	"strings"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/containerd/containerd/content"
    28  	"github.com/containerd/containerd/errdefs"
    29  	"github.com/containerd/containerd/filters"
    30  	"github.com/containerd/containerd/labels"
    31  	"github.com/containerd/containerd/log"
    32  	"github.com/containerd/containerd/metadata/boltutil"
    33  	"github.com/containerd/containerd/namespaces"
    34  	digest "github.com/opencontainers/go-digest"
    35  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    36  	"github.com/pkg/errors"
    37  	bolt "go.etcd.io/bbolt"
    38  )
    39  
    40  type contentStore struct {
    41  	content.Store
    42  	db     *DB
    43  	shared bool
    44  	l      sync.RWMutex
    45  }
    46  
    47  // newContentStore returns a namespaced content store using an existing
    48  // content store interface.
    49  // policy defines the sharing behavior for content between namespaces. Both
    50  // modes will result in shared storage in the backend for committed. Choose
    51  // "shared" to prevent separate namespaces from having to pull the same content
    52  // twice.  Choose "isolated" if the content must not be shared between
    53  // namespaces.
    54  //
    55  // If the policy is "shared", writes will try to resolve the "expected" digest
    56  // against the backend, allowing imports of content from other namespaces. In
    57  // "isolated" mode, the client must prove they have the content by providing
    58  // the entire blob before the content can be added to another namespace.
    59  //
    60  // Since we have only two policies right now, it's simpler using bool to
    61  // represent it internally.
    62  func newContentStore(db *DB, shared bool, cs content.Store) *contentStore {
    63  	return &contentStore{
    64  		Store:  cs,
    65  		db:     db,
    66  		shared: shared,
    67  	}
    68  }
    69  
    70  func (cs *contentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
    71  	ns, err := namespaces.NamespaceRequired(ctx)
    72  	if err != nil {
    73  		return content.Info{}, err
    74  	}
    75  
    76  	var info content.Info
    77  	if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
    78  		bkt := getBlobBucket(tx, ns, dgst)
    79  		if bkt == nil {
    80  			return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
    81  		}
    82  
    83  		info.Digest = dgst
    84  		return readInfo(&info, bkt)
    85  	}); err != nil {
    86  		return content.Info{}, err
    87  	}
    88  
    89  	return info, nil
    90  }
    91  
    92  func (cs *contentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
    93  	ns, err := namespaces.NamespaceRequired(ctx)
    94  	if err != nil {
    95  		return content.Info{}, err
    96  	}
    97  
    98  	cs.l.RLock()
    99  	defer cs.l.RUnlock()
   100  
   101  	updated := content.Info{
   102  		Digest: info.Digest,
   103  	}
   104  	if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
   105  		bkt := getBlobBucket(tx, ns, info.Digest)
   106  		if bkt == nil {
   107  			return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", info.Digest)
   108  		}
   109  
   110  		if err := readInfo(&updated, bkt); err != nil {
   111  			return errors.Wrapf(err, "info %q", info.Digest)
   112  		}
   113  
   114  		if len(fieldpaths) > 0 {
   115  			for _, path := range fieldpaths {
   116  				if strings.HasPrefix(path, "labels.") {
   117  					if updated.Labels == nil {
   118  						updated.Labels = map[string]string{}
   119  					}
   120  
   121  					key := strings.TrimPrefix(path, "labels.")
   122  					updated.Labels[key] = info.Labels[key]
   123  					continue
   124  				}
   125  
   126  				switch path {
   127  				case "labels":
   128  					updated.Labels = info.Labels
   129  				default:
   130  					return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on content info %q", path, info.Digest)
   131  				}
   132  			}
   133  		} else {
   134  			// Set mutable fields
   135  			updated.Labels = info.Labels
   136  		}
   137  		if err := validateInfo(&updated); err != nil {
   138  			return err
   139  		}
   140  
   141  		updated.UpdatedAt = time.Now().UTC()
   142  		return writeInfo(&updated, bkt)
   143  	}); err != nil {
   144  		return content.Info{}, err
   145  	}
   146  	return updated, nil
   147  }
   148  
   149  func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc, fs ...string) error {
   150  	ns, err := namespaces.NamespaceRequired(ctx)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	filter, err := filters.ParseAll(fs...)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	// TODO: Batch results to keep from reading all info into memory
   161  	var infos []content.Info
   162  	if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
   163  		bkt := getBlobsBucket(tx, ns)
   164  		if bkt == nil {
   165  			return nil
   166  		}
   167  
   168  		return bkt.ForEach(func(k, v []byte) error {
   169  			dgst, err := digest.Parse(string(k))
   170  			if err != nil {
   171  				// Not a digest, skip
   172  				return nil
   173  			}
   174  			bbkt := bkt.Bucket(k)
   175  			if bbkt == nil {
   176  				return nil
   177  			}
   178  			info := content.Info{
   179  				Digest: dgst,
   180  			}
   181  			if err := readInfo(&info, bkt.Bucket(k)); err != nil {
   182  				return err
   183  			}
   184  			if filter.Match(adaptContentInfo(info)) {
   185  				infos = append(infos, info)
   186  			}
   187  			return nil
   188  		})
   189  	}); err != nil {
   190  		return err
   191  	}
   192  
   193  	for _, info := range infos {
   194  		if err := fn(info); err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func (cs *contentStore) Delete(ctx context.Context, dgst digest.Digest) error {
   203  	ns, err := namespaces.NamespaceRequired(ctx)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	cs.l.RLock()
   209  	defer cs.l.RUnlock()
   210  
   211  	return update(ctx, cs.db, func(tx *bolt.Tx) error {
   212  		bkt := getBlobBucket(tx, ns, dgst)
   213  		if bkt == nil {
   214  			return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
   215  		}
   216  
   217  		if err := getBlobsBucket(tx, ns).DeleteBucket([]byte(dgst.String())); err != nil {
   218  			return err
   219  		}
   220  		if err := removeContentLease(ctx, tx, dgst); err != nil {
   221  			return err
   222  		}
   223  
   224  		// Mark content store as dirty for triggering garbage collection
   225  		atomic.AddUint32(&cs.db.dirty, 1)
   226  		cs.db.dirtyCS = true
   227  
   228  		return nil
   229  	})
   230  }
   231  
   232  func (cs *contentStore) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) {
   233  	ns, err := namespaces.NamespaceRequired(ctx)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	filter, err := filters.ParseAll(fs...)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	brefs := map[string]string{}
   244  	if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
   245  		bkt := getIngestsBucket(tx, ns)
   246  		if bkt == nil {
   247  			return nil
   248  		}
   249  
   250  		return bkt.ForEach(func(k, v []byte) error {
   251  			if v == nil {
   252  				// TODO(dmcgowan): match name and potentially labels here
   253  				brefs[string(k)] = string(bkt.Bucket(k).Get(bucketKeyRef))
   254  			}
   255  			return nil
   256  		})
   257  	}); err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	statuses := make([]content.Status, 0, len(brefs))
   262  	for k, bref := range brefs {
   263  		status, err := cs.Store.Status(ctx, bref)
   264  		if err != nil {
   265  			if errdefs.IsNotFound(err) {
   266  				continue
   267  			}
   268  			return nil, err
   269  		}
   270  		status.Ref = k
   271  
   272  		if filter.Match(adaptContentStatus(status)) {
   273  			statuses = append(statuses, status)
   274  		}
   275  	}
   276  
   277  	return statuses, nil
   278  
   279  }
   280  
   281  func getRef(tx *bolt.Tx, ns, ref string) string {
   282  	bkt := getIngestBucket(tx, ns, ref)
   283  	if bkt == nil {
   284  		return ""
   285  	}
   286  	v := bkt.Get(bucketKeyRef)
   287  	if len(v) == 0 {
   288  		return ""
   289  	}
   290  	return string(v)
   291  }
   292  
   293  func (cs *contentStore) Status(ctx context.Context, ref string) (content.Status, error) {
   294  	ns, err := namespaces.NamespaceRequired(ctx)
   295  	if err != nil {
   296  		return content.Status{}, err
   297  	}
   298  
   299  	var bref string
   300  	if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
   301  		bref = getRef(tx, ns, ref)
   302  		if bref == "" {
   303  			return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
   304  		}
   305  
   306  		return nil
   307  	}); err != nil {
   308  		return content.Status{}, err
   309  	}
   310  
   311  	st, err := cs.Store.Status(ctx, bref)
   312  	if err != nil {
   313  		return content.Status{}, err
   314  	}
   315  	st.Ref = ref
   316  	return st, nil
   317  }
   318  
   319  func (cs *contentStore) Abort(ctx context.Context, ref string) error {
   320  	ns, err := namespaces.NamespaceRequired(ctx)
   321  	if err != nil {
   322  		return err
   323  	}
   324  
   325  	cs.l.RLock()
   326  	defer cs.l.RUnlock()
   327  
   328  	return update(ctx, cs.db, func(tx *bolt.Tx) error {
   329  		ibkt := getIngestsBucket(tx, ns)
   330  		if ibkt == nil {
   331  			return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
   332  		}
   333  		bkt := ibkt.Bucket([]byte(ref))
   334  		if bkt == nil {
   335  			return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
   336  		}
   337  		bref := string(bkt.Get(bucketKeyRef))
   338  		if bref == "" {
   339  			return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
   340  		}
   341  		expected := string(bkt.Get(bucketKeyExpected))
   342  		if err := ibkt.DeleteBucket([]byte(ref)); err != nil {
   343  			return err
   344  		}
   345  
   346  		if err := removeIngestLease(ctx, tx, ref); err != nil {
   347  			return err
   348  		}
   349  
   350  		// if not shared content, delete active ingest on backend
   351  		if expected == "" {
   352  			return cs.Store.Abort(ctx, bref)
   353  		}
   354  
   355  		return nil
   356  	})
   357  
   358  }
   359  
   360  func (cs *contentStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
   361  	var wOpts content.WriterOpts
   362  	for _, opt := range opts {
   363  		if err := opt(&wOpts); err != nil {
   364  			return nil, err
   365  		}
   366  	}
   367  	// TODO(AkihiroSuda): we could create a random string or one calculated based on the context
   368  	// https://github.com/containerd/containerd/issues/2129#issuecomment-380255019
   369  	if wOpts.Ref == "" {
   370  		return nil, errors.Wrap(errdefs.ErrInvalidArgument, "ref must not be empty")
   371  	}
   372  	ns, err := namespaces.NamespaceRequired(ctx)
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  
   377  	cs.l.RLock()
   378  	defer cs.l.RUnlock()
   379  
   380  	var (
   381  		w      content.Writer
   382  		exists bool
   383  		bref   string
   384  	)
   385  	if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
   386  		var shared bool
   387  		if wOpts.Desc.Digest != "" {
   388  			cbkt := getBlobBucket(tx, ns, wOpts.Desc.Digest)
   389  			if cbkt != nil {
   390  				// Add content to lease to prevent other reference removals
   391  				// from effecting this object during a provided lease
   392  				if err := addContentLease(ctx, tx, wOpts.Desc.Digest); err != nil {
   393  					return errors.Wrap(err, "unable to lease content")
   394  				}
   395  				// Return error outside of transaction to ensure
   396  				// commit succeeds with the lease.
   397  				exists = true
   398  				return nil
   399  			}
   400  
   401  			if cs.shared {
   402  				if st, err := cs.Store.Info(ctx, wOpts.Desc.Digest); err == nil {
   403  					// Ensure the expected size is the same, it is likely
   404  					// an error if the size is mismatched but the caller
   405  					// must resolve this on commit
   406  					if wOpts.Desc.Size == 0 || wOpts.Desc.Size == st.Size {
   407  						shared = true
   408  						wOpts.Desc.Size = st.Size
   409  					}
   410  				}
   411  			}
   412  		}
   413  
   414  		bkt, err := createIngestBucket(tx, ns, wOpts.Ref)
   415  		if err != nil {
   416  			return err
   417  		}
   418  
   419  		leased, err := addIngestLease(ctx, tx, wOpts.Ref)
   420  		if err != nil {
   421  			return err
   422  		}
   423  
   424  		brefb := bkt.Get(bucketKeyRef)
   425  		if brefb == nil {
   426  			sid, err := bkt.NextSequence()
   427  			if err != nil {
   428  				return err
   429  			}
   430  
   431  			bref = createKey(sid, ns, wOpts.Ref)
   432  			if err := bkt.Put(bucketKeyRef, []byte(bref)); err != nil {
   433  				return err
   434  			}
   435  		} else {
   436  			bref = string(brefb)
   437  		}
   438  		if !leased {
   439  			// Add timestamp to allow aborting once stale
   440  			// When lease is set the ingest should be aborted
   441  			// after lease it belonged to is deleted.
   442  			// Expiration can be configurable in the future to
   443  			// give more control to the daemon, however leases
   444  			// already give users more control of expiration.
   445  			expireAt := time.Now().UTC().Add(24 * time.Hour)
   446  			if err := writeExpireAt(expireAt, bkt); err != nil {
   447  				return err
   448  			}
   449  		}
   450  
   451  		if shared {
   452  			if err := bkt.Put(bucketKeyExpected, []byte(wOpts.Desc.Digest)); err != nil {
   453  				return err
   454  			}
   455  		} else {
   456  			// Do not use the passed in expected value here since it was
   457  			// already checked against the user metadata. The content must
   458  			// be committed in the namespace before it will be seen as
   459  			// available in the current namespace.
   460  			desc := wOpts.Desc
   461  			desc.Digest = ""
   462  			w, err = cs.Store.Writer(ctx, content.WithRef(bref), content.WithDescriptor(desc))
   463  		}
   464  		return err
   465  	}); err != nil {
   466  		return nil, err
   467  	}
   468  	if exists {
   469  		return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", wOpts.Desc.Digest)
   470  	}
   471  
   472  	return &namespacedWriter{
   473  		ctx:       ctx,
   474  		ref:       wOpts.Ref,
   475  		namespace: ns,
   476  		db:        cs.db,
   477  		provider:  cs.Store,
   478  		l:         &cs.l,
   479  		w:         w,
   480  		bref:      bref,
   481  		started:   time.Now(),
   482  		desc:      wOpts.Desc,
   483  	}, nil
   484  }
   485  
   486  type namespacedWriter struct {
   487  	ctx       context.Context
   488  	ref       string
   489  	namespace string
   490  	db        transactor
   491  	provider  interface {
   492  		content.Provider
   493  		content.Ingester
   494  	}
   495  	l *sync.RWMutex
   496  
   497  	w content.Writer
   498  
   499  	bref    string
   500  	started time.Time
   501  	desc    ocispec.Descriptor
   502  }
   503  
   504  func (nw *namespacedWriter) Close() error {
   505  	if nw.w != nil {
   506  		return nw.w.Close()
   507  	}
   508  	return nil
   509  }
   510  
   511  func (nw *namespacedWriter) Write(p []byte) (int, error) {
   512  	// if no writer, first copy and unshare before performing write
   513  	if nw.w == nil {
   514  		if len(p) == 0 {
   515  			return 0, nil
   516  		}
   517  
   518  		if err := nw.createAndCopy(nw.ctx, nw.desc); err != nil {
   519  			return 0, err
   520  		}
   521  	}
   522  
   523  	return nw.w.Write(p)
   524  }
   525  
   526  func (nw *namespacedWriter) Digest() digest.Digest {
   527  	if nw.w != nil {
   528  		return nw.w.Digest()
   529  	}
   530  	return nw.desc.Digest
   531  }
   532  
   533  func (nw *namespacedWriter) Truncate(size int64) error {
   534  	if nw.w != nil {
   535  		return nw.w.Truncate(size)
   536  	}
   537  	desc := nw.desc
   538  	desc.Size = size
   539  	desc.Digest = ""
   540  	return nw.createAndCopy(nw.ctx, desc)
   541  }
   542  
   543  func (nw *namespacedWriter) createAndCopy(ctx context.Context, desc ocispec.Descriptor) error {
   544  	nwDescWithoutDigest := desc
   545  	nwDescWithoutDigest.Digest = ""
   546  	w, err := nw.provider.Writer(ctx, content.WithRef(nw.bref), content.WithDescriptor(nwDescWithoutDigest))
   547  	if err != nil {
   548  		return err
   549  	}
   550  
   551  	if desc.Size > 0 {
   552  		ra, err := nw.provider.ReaderAt(ctx, nw.desc)
   553  		if err != nil {
   554  			return err
   555  		}
   556  		defer ra.Close()
   557  
   558  		if err := content.CopyReaderAt(w, ra, desc.Size); err != nil {
   559  			nw.w.Close()
   560  			nw.w = nil
   561  			return err
   562  		}
   563  	}
   564  	nw.w = w
   565  
   566  	return nil
   567  }
   568  
   569  func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
   570  	ctx = namespaces.WithNamespace(ctx, nw.namespace)
   571  
   572  	nw.l.RLock()
   573  	defer nw.l.RUnlock()
   574  
   575  	var innerErr error
   576  
   577  	if err := update(ctx, nw.db, func(tx *bolt.Tx) error {
   578  		dgst, err := nw.commit(ctx, tx, size, expected, opts...)
   579  		if err != nil {
   580  			if !errdefs.IsAlreadyExists(err) {
   581  				return err
   582  			}
   583  			innerErr = err
   584  		}
   585  		bkt := getIngestsBucket(tx, nw.namespace)
   586  		if bkt != nil {
   587  			if err := bkt.DeleteBucket([]byte(nw.ref)); err != nil && err != bolt.ErrBucketNotFound {
   588  				return err
   589  			}
   590  		}
   591  		if err := removeIngestLease(ctx, tx, nw.ref); err != nil {
   592  			return err
   593  		}
   594  		return addContentLease(ctx, tx, dgst)
   595  	}); err != nil {
   596  		return err
   597  	}
   598  
   599  	return innerErr
   600  }
   601  
   602  func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, expected digest.Digest, opts ...content.Opt) (digest.Digest, error) {
   603  	var base content.Info
   604  	for _, opt := range opts {
   605  		if err := opt(&base); err != nil {
   606  			if nw.w != nil {
   607  				nw.w.Close()
   608  			}
   609  			return "", err
   610  		}
   611  	}
   612  	if err := validateInfo(&base); err != nil {
   613  		if nw.w != nil {
   614  			nw.w.Close()
   615  		}
   616  		return "", err
   617  	}
   618  
   619  	var actual digest.Digest
   620  	if nw.w == nil {
   621  		if size != 0 && size != nw.desc.Size {
   622  			return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, nw.desc.Size, size)
   623  		}
   624  		if expected != "" && expected != nw.desc.Digest {
   625  			return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q unexpected digest", nw.ref)
   626  		}
   627  		size = nw.desc.Size
   628  		actual = nw.desc.Digest
   629  	} else {
   630  		status, err := nw.w.Status()
   631  		if err != nil {
   632  			nw.w.Close()
   633  			return "", err
   634  		}
   635  		if size != 0 && size != status.Offset {
   636  			nw.w.Close()
   637  			return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, status.Offset, size)
   638  		}
   639  		size = status.Offset
   640  
   641  		if err := nw.w.Commit(ctx, size, expected); err != nil && !errdefs.IsAlreadyExists(err) {
   642  			return "", err
   643  		}
   644  		actual = nw.w.Digest()
   645  	}
   646  
   647  	bkt, err := createBlobBucket(tx, nw.namespace, actual)
   648  	if err != nil {
   649  		if err == bolt.ErrBucketExists {
   650  			return actual, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
   651  		}
   652  		return "", err
   653  	}
   654  
   655  	commitTime := time.Now().UTC()
   656  
   657  	sizeEncoded, err := encodeInt(size)
   658  	if err != nil {
   659  		return "", err
   660  	}
   661  
   662  	if err := boltutil.WriteTimestamps(bkt, commitTime, commitTime); err != nil {
   663  		return "", err
   664  	}
   665  	if err := boltutil.WriteLabels(bkt, base.Labels); err != nil {
   666  		return "", err
   667  	}
   668  	return actual, bkt.Put(bucketKeySize, sizeEncoded)
   669  }
   670  
   671  func (nw *namespacedWriter) Status() (st content.Status, err error) {
   672  	if nw.w != nil {
   673  		st, err = nw.w.Status()
   674  	} else {
   675  		st.Offset = nw.desc.Size
   676  		st.Total = nw.desc.Size
   677  		st.StartedAt = nw.started
   678  		st.UpdatedAt = nw.started
   679  		st.Expected = nw.desc.Digest
   680  	}
   681  	if err == nil {
   682  		st.Ref = nw.ref
   683  	}
   684  	return
   685  }
   686  
   687  func (cs *contentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
   688  	if err := cs.checkAccess(ctx, desc.Digest); err != nil {
   689  		return nil, err
   690  	}
   691  	return cs.Store.ReaderAt(ctx, desc)
   692  }
   693  
   694  func (cs *contentStore) checkAccess(ctx context.Context, dgst digest.Digest) error {
   695  	ns, err := namespaces.NamespaceRequired(ctx)
   696  	if err != nil {
   697  		return err
   698  	}
   699  
   700  	return view(ctx, cs.db, func(tx *bolt.Tx) error {
   701  		bkt := getBlobBucket(tx, ns, dgst)
   702  		if bkt == nil {
   703  			return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst)
   704  		}
   705  		return nil
   706  	})
   707  }
   708  
   709  func validateInfo(info *content.Info) error {
   710  	for k, v := range info.Labels {
   711  		if err := labels.Validate(k, v); err == nil {
   712  			return errors.Wrapf(err, "info.Labels")
   713  		}
   714  	}
   715  
   716  	return nil
   717  }
   718  
   719  func readInfo(info *content.Info, bkt *bolt.Bucket) error {
   720  	if err := boltutil.ReadTimestamps(bkt, &info.CreatedAt, &info.UpdatedAt); err != nil {
   721  		return err
   722  	}
   723  
   724  	labels, err := boltutil.ReadLabels(bkt)
   725  	if err != nil {
   726  		return err
   727  	}
   728  	info.Labels = labels
   729  
   730  	if v := bkt.Get(bucketKeySize); len(v) > 0 {
   731  		info.Size, _ = binary.Varint(v)
   732  	}
   733  
   734  	return nil
   735  }
   736  
   737  func writeInfo(info *content.Info, bkt *bolt.Bucket) error {
   738  	if err := boltutil.WriteTimestamps(bkt, info.CreatedAt, info.UpdatedAt); err != nil {
   739  		return err
   740  	}
   741  
   742  	if err := boltutil.WriteLabels(bkt, info.Labels); err != nil {
   743  		return errors.Wrapf(err, "writing labels for info %v", info.Digest)
   744  	}
   745  
   746  	// Write size
   747  	sizeEncoded, err := encodeInt(info.Size)
   748  	if err != nil {
   749  		return err
   750  	}
   751  
   752  	return bkt.Put(bucketKeySize, sizeEncoded)
   753  }
   754  
   755  func readExpireAt(bkt *bolt.Bucket) (*time.Time, error) {
   756  	v := bkt.Get(bucketKeyExpireAt)
   757  	if v == nil {
   758  		return nil, nil
   759  	}
   760  	t := &time.Time{}
   761  	if err := t.UnmarshalBinary(v); err != nil {
   762  		return nil, err
   763  	}
   764  	return t, nil
   765  }
   766  
   767  func writeExpireAt(expire time.Time, bkt *bolt.Bucket) error {
   768  	expireAt, err := expire.MarshalBinary()
   769  	if err != nil {
   770  		return err
   771  	}
   772  	return bkt.Put(bucketKeyExpireAt, expireAt)
   773  }
   774  
   775  func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, err error) {
   776  	cs.l.Lock()
   777  	t1 := time.Now()
   778  	defer func() {
   779  		if err == nil {
   780  			d = time.Since(t1)
   781  		}
   782  		cs.l.Unlock()
   783  	}()
   784  
   785  	contentSeen := map[string]struct{}{}
   786  	ingestSeen := map[string]struct{}{}
   787  	if err := cs.db.View(func(tx *bolt.Tx) error {
   788  		v1bkt := tx.Bucket(bucketKeyVersion)
   789  		if v1bkt == nil {
   790  			return nil
   791  		}
   792  
   793  		// iterate through each namespace
   794  		v1c := v1bkt.Cursor()
   795  
   796  		for k, v := v1c.First(); k != nil; k, v = v1c.Next() {
   797  			if v != nil {
   798  				continue
   799  			}
   800  
   801  			cbkt := v1bkt.Bucket(k).Bucket(bucketKeyObjectContent)
   802  			if cbkt == nil {
   803  				continue
   804  			}
   805  			bbkt := cbkt.Bucket(bucketKeyObjectBlob)
   806  			if bbkt != nil {
   807  				if err := bbkt.ForEach(func(ck, cv []byte) error {
   808  					if cv == nil {
   809  						contentSeen[string(ck)] = struct{}{}
   810  					}
   811  					return nil
   812  				}); err != nil {
   813  					return err
   814  				}
   815  			}
   816  
   817  			ibkt := cbkt.Bucket(bucketKeyObjectIngests)
   818  			if ibkt != nil {
   819  				if err := ibkt.ForEach(func(ref, v []byte) error {
   820  					if v == nil {
   821  						bkt := ibkt.Bucket(ref)
   822  						// expected here may be from a different namespace
   823  						// so much be explicitly retained from the ingest
   824  						// in case it was removed from the other namespace
   825  						expected := bkt.Get(bucketKeyExpected)
   826  						if len(expected) > 0 {
   827  							contentSeen[string(expected)] = struct{}{}
   828  						}
   829  						bref := bkt.Get(bucketKeyRef)
   830  						if len(bref) > 0 {
   831  							ingestSeen[string(bref)] = struct{}{}
   832  						}
   833  					}
   834  					return nil
   835  				}); err != nil {
   836  					return err
   837  				}
   838  			}
   839  		}
   840  
   841  		return nil
   842  	}); err != nil {
   843  		return 0, err
   844  	}
   845  
   846  	err = cs.Store.Walk(ctx, func(info content.Info) error {
   847  		if _, ok := contentSeen[info.Digest.String()]; !ok {
   848  			if err := cs.Store.Delete(ctx, info.Digest); err != nil {
   849  				return err
   850  			}
   851  			log.G(ctx).WithField("digest", info.Digest).Debug("removed content")
   852  		}
   853  		return nil
   854  	})
   855  	if err != nil {
   856  		return
   857  	}
   858  
   859  	// If the content store has implemented a more efficient walk function
   860  	// then use that else fallback to reading all statuses which may
   861  	// cause reading of unneeded metadata.
   862  	type statusWalker interface {
   863  		WalkStatusRefs(context.Context, func(string) error) error
   864  	}
   865  	if w, ok := cs.Store.(statusWalker); ok {
   866  		err = w.WalkStatusRefs(ctx, func(ref string) error {
   867  			if _, ok := ingestSeen[ref]; !ok {
   868  				if err := cs.Store.Abort(ctx, ref); err != nil {
   869  					return err
   870  				}
   871  				log.G(ctx).WithField("ref", ref).Debug("cleanup aborting ingest")
   872  			}
   873  			return nil
   874  		})
   875  	} else {
   876  		var statuses []content.Status
   877  		statuses, err = cs.Store.ListStatuses(ctx)
   878  		if err != nil {
   879  			return 0, err
   880  		}
   881  		for _, status := range statuses {
   882  			if _, ok := ingestSeen[status.Ref]; !ok {
   883  				if err = cs.Store.Abort(ctx, status.Ref); err != nil {
   884  					return
   885  				}
   886  				log.G(ctx).WithField("ref", status.Ref).Debug("cleanup aborting ingest")
   887  			}
   888  		}
   889  	}
   890  	return
   891  }