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	return ApplyLayersWithOpts(ctx, layers, sn, a, nil)
52}
53
54// ApplyLayersWithOpts applies all the layers using the given snapshotter, applier, and apply opts.
55// The returned result is a chain id digest representing all the applied layers.
56// Layers are applied in order they are given, making the first layer the
57// bottom-most layer in the layer chain.
58func ApplyLayersWithOpts(ctx context.Context, layers []Layer, sn snapshots.Snapshotter, a diff.Applier, applyOpts []diff.ApplyOpt) (digest.Digest, error) {
59	chain := make([]digest.Digest, len(layers))
60	for i, layer := range layers {
61		chain[i] = layer.Diff.Digest
62	}
63	chainID := identity.ChainID(chain)
64
65	// Just stat top layer, remaining layers will have their existence checked
66	// on prepare. Calling prepare on upper layers first guarantees that upper
67	// layers are not removed while calling stat on lower layers
68	_, err := sn.Stat(ctx, chainID.String())
69	if err != nil {
70		if !errdefs.IsNotFound(err) {
71			return "", errors.Wrapf(err, "failed to stat snapshot %s", chainID)
72		}
73
74		if err := applyLayers(ctx, layers, chain, sn, a, nil, applyOpts); err != nil && !errdefs.IsAlreadyExists(err) {
75			return "", err
76		}
77	}
78
79	return chainID, nil
80}
81
82// ApplyLayer applies a single layer on top of the given provided layer chain,
83// using the provided snapshotter and applier. If the layer was unpacked true
84// is returned, if the layer already exists false is returned.
85func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) (bool, error) {
86	return ApplyLayerWithOpts(ctx, layer, chain, sn, a, opts, nil)
87}
88
89// ApplyLayerWithOpts applies a single layer on top of the given provided layer chain,
90// using the provided snapshotter, applier, and apply opts. If the layer was unpacked true
91// is returned, if the layer already exists false is returned.
92func ApplyLayerWithOpts(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts []snapshots.Opt, applyOpts []diff.ApplyOpt) (bool, error) {
93	var (
94		chainID = identity.ChainID(append(chain, layer.Diff.Digest)).String()
95		applied bool
96	)
97	if _, err := sn.Stat(ctx, chainID); err != nil {
98		if !errdefs.IsNotFound(err) {
99			return false, errors.Wrapf(err, "failed to stat snapshot %s", chainID)
100		}
101
102		if err := applyLayers(ctx, []Layer{layer}, append(chain, layer.Diff.Digest), sn, a, opts, applyOpts); err != nil {
103			if !errdefs.IsAlreadyExists(err) {
104				return false, err
105			}
106		} else {
107			applied = true
108		}
109	}
110	return applied, nil
111
112}
113
114func applyLayers(ctx context.Context, layers []Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts []snapshots.Opt, applyOpts []diff.ApplyOpt) error {
115	var (
116		parent  = identity.ChainID(chain[:len(chain)-1])
117		chainID = identity.ChainID(chain)
118		layer   = layers[len(layers)-1]
119		diff    ocispec.Descriptor
120		key     string
121		mounts  []mount.Mount
122		err     error
123	)
124
125	for {
126		key = fmt.Sprintf(snapshots.UnpackKeyFormat, uniquePart(), chainID)
127
128		// Prepare snapshot with from parent, label as root
129		mounts, err = sn.Prepare(ctx, key, parent.String(), opts...)
130		if err != nil {
131			if errdefs.IsNotFound(err) && len(layers) > 1 {
132				if err := applyLayers(ctx, layers[:len(layers)-1], chain[:len(chain)-1], sn, a, opts, applyOpts); err != nil {
133					if !errdefs.IsAlreadyExists(err) {
134						return err
135					}
136				}
137				// Do no try applying layers again
138				layers = nil
139				continue
140			} else if errdefs.IsAlreadyExists(err) {
141				// Try a different key
142				continue
143			}
144
145			// Already exists should have the caller retry
146			return errors.Wrapf(err, "failed to prepare extraction snapshot %q", key)
147
148		}
149		break
150	}
151	defer func() {
152		if err != nil {
153			if !errdefs.IsAlreadyExists(err) {
154				log.G(ctx).WithError(err).WithField("key", key).Infof("apply failure, attempting cleanup")
155			}
156
157			if rerr := sn.Remove(ctx, key); rerr != nil {
158				log.G(ctx).WithError(rerr).WithField("key", key).Warnf("extraction snapshot removal failed")
159			}
160		}
161	}()
162
163	diff, err = a.Apply(ctx, layer.Blob, mounts, applyOpts...)
164	if err != nil {
165		err = errors.Wrapf(err, "failed to extract layer %s", layer.Diff.Digest)
166		return err
167	}
168	if diff.Digest != layer.Diff.Digest {
169		err = errors.Errorf("wrong diff id calculated on extraction %q", diff.Digest)
170		return err
171	}
172
173	if err = sn.Commit(ctx, chainID.String(), key, opts...); err != nil {
174		err = errors.Wrapf(err, "failed to commit snapshot %s", key)
175		return err
176	}
177
178	return nil
179}
180
181func uniquePart() string {
182	t := time.Now()
183	var b [3]byte
184	// Ignore read failures, just decreases uniqueness
185	rand.Read(b[:])
186	return fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
187}
188