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
17package rootfs
18
19import (
20	"context"
21	"encoding/base64"
22	"fmt"
23	"math/rand"
24	"time"
25
26	"github.com/containerd/containerd/diff"
27	"github.com/containerd/containerd/errdefs"
28	"github.com/containerd/containerd/log"
29	"github.com/containerd/containerd/mount"
30	"github.com/containerd/containerd/snapshots"
31	"github.com/opencontainers/go-digest"
32	"github.com/opencontainers/image-spec/identity"
33	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
34	"github.com/pkg/errors"
35)
36
37// Layer represents the descriptors for a layer diff. These descriptions
38// include the descriptor for the uncompressed tar diff as well as a blob
39// used to transport that tar. The blob descriptor may or may not describe
40// a compressed object.
41type Layer struct {
42	Diff ocispec.Descriptor
43	Blob ocispec.Descriptor
44}
45
46// ApplyLayers applies all the layers using the given snapshotter and applier.
47// The returned result is a chain id digest representing all the applied layers.
48// Layers are applied in order they are given, making the first layer the
49// bottom-most layer in the layer chain.
50func ApplyLayers(ctx context.Context, layers []Layer, sn snapshots.Snapshotter, a diff.Applier) (digest.Digest, error) {
51	chain := make([]digest.Digest, len(layers))
52	for i, layer := range layers {
53		chain[i] = layer.Diff.Digest
54	}
55	chainID := identity.ChainID(chain)
56
57	// Just stat top layer, remaining layers will have their existence checked
58	// on prepare. Calling prepare on upper layers first guarantees that upper
59	// layers are not removed while calling stat on lower layers
60	_, err := sn.Stat(ctx, chainID.String())
61	if err != nil {
62		if !errdefs.IsNotFound(err) {
63			return "", errors.Wrapf(err, "failed to stat snapshot %s", chainID)
64		}
65
66		if err := applyLayers(ctx, layers, chain, sn, a); err != nil && !errdefs.IsAlreadyExists(err) {
67			return "", err
68		}
69	}
70
71	return chainID, nil
72}
73
74// ApplyLayer applies a single layer on top of the given provided layer chain,
75// using the provided snapshotter and applier. If the layer was unpacked true
76// is returned, if the layer already exists false is returned.
77func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) (bool, error) {
78	var (
79		chainID = identity.ChainID(append(chain, layer.Diff.Digest)).String()
80		applied bool
81	)
82	if _, err := sn.Stat(ctx, chainID); err != nil {
83		if !errdefs.IsNotFound(err) {
84			return false, errors.Wrapf(err, "failed to stat snapshot %s", chainID)
85		}
86
87		if err := applyLayers(ctx, []Layer{layer}, append(chain, layer.Diff.Digest), sn, a, opts...); err != nil {
88			if !errdefs.IsAlreadyExists(err) {
89				return false, err
90			}
91		} else {
92			applied = true
93		}
94	}
95	return applied, nil
96}
97
98func applyLayers(ctx context.Context, layers []Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) error {
99	var (
100		parent  = identity.ChainID(chain[:len(chain)-1])
101		chainID = identity.ChainID(chain)
102		layer   = layers[len(layers)-1]
103		diff    ocispec.Descriptor
104		key     string
105		mounts  []mount.Mount
106		err     error
107	)
108
109	for {
110		key = fmt.Sprintf("extract-%s %s", uniquePart(), chainID)
111
112		// Prepare snapshot with from parent, label as root
113		mounts, err = sn.Prepare(ctx, key, parent.String(), opts...)
114		if err != nil {
115			if errdefs.IsNotFound(err) && len(layers) > 1 {
116				if err := applyLayers(ctx, layers[:len(layers)-1], chain[:len(chain)-1], sn, a); err != nil {
117					if !errdefs.IsAlreadyExists(err) {
118						return err
119					}
120				}
121				// Do no try applying layers again
122				layers = nil
123				continue
124			} else if errdefs.IsAlreadyExists(err) {
125				// Try a different key
126				continue
127			}
128
129			// Already exists should have the caller retry
130			return errors.Wrapf(err, "failed to prepare extraction snapshot %q", key)
131
132		}
133		break
134	}
135	defer func() {
136		if err != nil {
137			if !errdefs.IsAlreadyExists(err) {
138				log.G(ctx).WithError(err).WithField("key", key).Infof("apply failure, attempting cleanup")
139			}
140
141			if rerr := sn.Remove(ctx, key); rerr != nil {
142				log.G(ctx).WithError(rerr).WithField("key", key).Warnf("extraction snapshot removal failed")
143			}
144		}
145	}()
146
147	diff, err = a.Apply(ctx, layer.Blob, mounts)
148	if err != nil {
149		err = errors.Wrapf(err, "failed to extract layer %s", layer.Diff.Digest)
150		return err
151	}
152	if diff.Digest != layer.Diff.Digest {
153		err = errors.Errorf("wrong diff id calculated on extraction %q", diff.Digest)
154		return err
155	}
156
157	if err = sn.Commit(ctx, chainID.String(), key, opts...); err != nil {
158		err = errors.Wrapf(err, "failed to commit snapshot %s", key)
159		return err
160	}
161
162	return nil
163}
164
165func uniquePart() string {
166	t := time.Now()
167	var b [3]byte
168	// Ignore read failures, just decreases uniqueness
169	rand.Read(b[:])
170	return fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
171}
172