Browse code

Bump containerd to d97a907f

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2018/09/19 01:05:20
Showing 73 changed files
... ...
@@ -115,12 +115,12 @@ github.com/googleapis/gax-go v2.0.0
115 115
 google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
116 116
 
117 117
 # containerd
118
-github.com/containerd/containerd v1.2.0-beta.2
118
+github.com/containerd/containerd d97a907f7f781c0ab8340877d8e6b53cc7f1c2f6
119 119
 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
120
-github.com/containerd/continuity c7c5070e6f6e090ab93b0a61eb921f2196fc3383
120
+github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
121 121
 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
122 122
 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
123
-github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031
123
+github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
124 124
 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
125 125
 github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
126 126
 github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
... ...
@@ -2,6 +2,7 @@
2 2
 
3 3
 [![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
4 4
 [![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
5
+[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/containerd/containerd?branch=master&svg=true)](https://ci.appveyor.com/project/mlaventure/containerd-3g73f?branch=master)
5 6
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
6 7
 [![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd)
7 8
 [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
... ...
@@ -223,7 +224,7 @@ This will be the best place to discuss design and implementation.
223 223
 
224 224
 For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
225 225
 
226
-**Slack:** https://dockr.ly/community
226
+**Slack:** https://join.slack.com/t/dockercommunity/shared_invite/enQtNDM4NjAwNDMyOTUwLWZlMDZmYWRjZjk4Zjc5ZGQ5NWZkOWI1Yjk2NGE3ZWVlYjYxM2VhYjczOWIyZDFhZTE3NTUwZWQzMjhmNGYyZTg
227 227
 
228 228
 ### Reporting security issues
229 229
 
... ...
@@ -141,7 +141,7 @@ type EventsClient interface {
141 141
 	// Forward sends an event that has already been packaged into an envelope
142 142
 	// with a timestamp and namespace.
143 143
 	//
144
-	// This is useful if earlier timestamping is required or when fowarding on
144
+	// This is useful if earlier timestamping is required or when forwarding on
145 145
 	// behalf of another component, namespace or publisher.
146 146
 	Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error)
147 147
 	// Subscribe to a stream of events, possibly returning only that match any
... ...
@@ -223,7 +223,7 @@ type EventsServer interface {
223 223
 	// Forward sends an event that has already been packaged into an envelope
224 224
 	// with a timestamp and namespace.
225 225
 	//
226
-	// This is useful if earlier timestamping is required or when fowarding on
226
+	// This is useful if earlier timestamping is required or when forwarding on
227 227
 	// behalf of another component, namespace or publisher.
228 228
 	Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error)
229 229
 	// Subscribe to a stream of events, possibly returning only that match any
... ...
@@ -20,7 +20,7 @@ service Events {
20 20
 	// Forward sends an event that has already been packaged into an envelope
21 21
 	// with a timestamp and namespace.
22 22
 	//
23
-	// This is useful if earlier timestamping is required or when fowarding on
23
+	// This is useful if earlier timestamping is required or when forwarding on
24 24
 	// behalf of another component, namespace or publisher.
25 25
 	rpc Forward(ForwardRequest) returns (google.protobuf.Empty);
26 26
 
... ...
@@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
141 141
 		if err != nil {
142 142
 			return nil, err
143 143
 		}
144
+		if streams.Stdin == nil {
145
+			fifos.Stdin = ""
146
+		}
147
+		if streams.Stdout == nil {
148
+			fifos.Stdout = ""
149
+		}
150
+		if streams.Stderr == nil {
151
+			fifos.Stderr = ""
152
+		}
144 153
 		return copyIO(fifos, streams)
145 154
 	}
146 155
 }
... ...
@@ -20,25 +20,21 @@ package containerd
20 20
 
21 21
 import (
22 22
 	"context"
23
-	"encoding/json"
24 23
 	"fmt"
25 24
 	"os"
26 25
 	"path/filepath"
27 26
 	"syscall"
28 27
 
29
-	"github.com/containerd/containerd/api/types"
30 28
 	"github.com/containerd/containerd/containers"
31 29
 	"github.com/containerd/containerd/content"
32 30
 	"github.com/containerd/containerd/errdefs"
33 31
 	"github.com/containerd/containerd/images"
34 32
 	"github.com/containerd/containerd/mount"
35 33
 	"github.com/containerd/containerd/platforms"
36
-	"github.com/containerd/containerd/runtime/linux/runctypes"
37 34
 	"github.com/gogo/protobuf/proto"
38 35
 	protobuf "github.com/gogo/protobuf/types"
39 36
 	"github.com/opencontainers/image-spec/identity"
40 37
 	"github.com/opencontainers/image-spec/specs-go/v1"
41
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
42 38
 	"github.com/pkg/errors"
43 39
 )
44 40
 
... ...
@@ -105,44 +101,6 @@ func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
105 105
 	}
106 106
 }
107 107
 
108
-// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
109
-// previous checkpoint. Additional software such as CRIU may be required to
110
-// restore a task from a checkpoint
111
-func WithTaskCheckpoint(im Image) NewTaskOpts {
112
-	return func(ctx context.Context, c *Client, info *TaskInfo) error {
113
-		desc := im.Target()
114
-		id := desc.Digest
115
-		index, err := decodeIndex(ctx, c.ContentStore(), desc)
116
-		if err != nil {
117
-			return err
118
-		}
119
-		for _, m := range index.Manifests {
120
-			if m.MediaType == images.MediaTypeContainerd1Checkpoint {
121
-				info.Checkpoint = &types.Descriptor{
122
-					MediaType: m.MediaType,
123
-					Size_:     m.Size,
124
-					Digest:    m.Digest,
125
-				}
126
-				return nil
127
-			}
128
-		}
129
-		return fmt.Errorf("checkpoint not found in index %s", id)
130
-	}
131
-}
132
-
133
-func decodeIndex(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (*v1.Index, error) {
134
-	var index v1.Index
135
-	p, err := content.ReadBlob(ctx, store, desc)
136
-	if err != nil {
137
-		return nil, err
138
-	}
139
-	if err := json.Unmarshal(p, &index); err != nil {
140
-		return nil, err
141
-	}
142
-
143
-	return &index, nil
144
-}
145
-
146 108
 // WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
147 109
 // filesystem to be used by a container with user namespaces
148 110
 func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
... ...
@@ -221,19 +179,3 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
221 221
 		return os.Lchown(path, u, g)
222 222
 	}
223 223
 }
224
-
225
-// WithNoPivotRoot instructs the runtime not to you pivot_root
226
-func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
227
-	if info.Options == nil {
228
-		info.Options = &runctypes.CreateOptions{
229
-			NoPivotRoot: true,
230
-		}
231
-		return nil
232
-	}
233
-	copts, ok := info.Options.(*runctypes.CreateOptions)
234
-	if !ok {
235
-		return errors.New("invalid options type, expected runctypes.CreateOptions")
236
-	}
237
-	copts.NoPivotRoot = true
238
-	return nil
239
-}
... ...
@@ -33,6 +33,8 @@ import (
33 33
 	"github.com/containerd/containerd/errdefs"
34 34
 	"github.com/containerd/containerd/filters"
35 35
 	"github.com/containerd/containerd/log"
36
+
37
+	"github.com/containerd/continuity"
36 38
 	digest "github.com/opencontainers/go-digest"
37 39
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
38 40
 	"github.com/pkg/errors"
... ...
@@ -651,5 +653,5 @@ func writeTimestampFile(p string, t time.Time) error {
651 651
 		return err
652 652
 	}
653 653
 
654
-	return ioutil.WriteFile(p, b, 0666)
654
+	return continuity.AtomicWriteFile(p, b, 0666)
655 655
 }
... ...
@@ -132,11 +132,11 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
132 132
 	// clean up!!
133 133
 	defer os.RemoveAll(w.path)
134 134
 
135
+	if _, err := os.Stat(target); err == nil {
136
+		// collision with the target file!
137
+		return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
138
+	}
135 139
 	if err := os.Rename(ingest, target); err != nil {
136
-		if os.IsExist(err) {
137
-			// collision with the target file!
138
-			return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
139
-		}
140 140
 		return err
141 141
 	}
142 142
 	commitTime := time.Now()
... ...
@@ -30,7 +30,7 @@ import (
30 30
 )
31 31
 
32 32
 // WithProfile receives the name of a file stored on disk comprising a json
33
-// formated seccomp profile, as specified by the opencontainers/runtime-spec.
33
+// formatted seccomp profile, as specified by the opencontainers/runtime-spec.
34 34
 // The profile is read from the file, unmarshaled, and set to the spec.
35 35
 func WithProfile(profile string) oci.SpecOpts {
36 36
 	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
... ...
@@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{}
52 52
 
53 53
 // Forward accepts an envelope to be direcly distributed on the exchange.
54 54
 //
55
-// This is useful when an event is forwaded on behalf of another namespace or
55
+// This is useful when an event is forwarded on behalf of another namespace or
56 56
 // when the event is propagated on behalf of another publisher.
57 57
 func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
58 58
 	if err := validateEnvelope(envelope); err != nil {
59 59
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+/*
1
+   Copyright The containerd Authors.
2
+
3
+   Licensed under the Apache License, Version 2.0 (the "License");
4
+   you may not use this file except in compliance with the License.
5
+   You may obtain a copy of the License at
6
+
7
+       http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+   Unless required by applicable law or agreed to in writing, software
10
+   distributed under the License is distributed on an "AS IS" BASIS,
11
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+   See the License for the specific language governing permissions and
13
+   limitations under the License.
14
+*/
15
+
16
+package containerd
17
+
18
+import (
19
+	"context"
20
+	"io"
21
+
22
+	"github.com/containerd/containerd/images"
23
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24
+)
25
+
26
+type exportOpts struct {
27
+}
28
+
29
+// ExportOpt allows the caller to specify export-specific options
30
+type ExportOpt func(c *exportOpts) error
31
+
32
+func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
33
+	var eopts exportOpts
34
+	for _, o := range opts {
35
+		if err := o(&eopts); err != nil {
36
+			return eopts, err
37
+		}
38
+	}
39
+	return eopts, nil
40
+}
41
+
42
+// Export exports an image to a Tar stream.
43
+// OCI format is used by default.
44
+// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
45
+// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
46
+func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
47
+	_, err := resolveExportOpt(opts...) // unused now
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+	pr, pw := io.Pipe()
52
+	go func() {
53
+		pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
54
+	}()
55
+	return pr, nil
56
+}
... ...
@@ -22,7 +22,6 @@ import (
22 22
 
23 23
 	"github.com/containerd/containerd/errdefs"
24 24
 	"github.com/containerd/containerd/images"
25
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
26 25
 )
27 26
 
28 27
 type importOpts struct {
... ...
@@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io
84 84
 	}
85 85
 	return images, nil
86 86
 }
87
-
88
-type exportOpts struct {
89
-}
90
-
91
-// ExportOpt allows the caller to specify export-specific options
92
-type ExportOpt func(c *exportOpts) error
93
-
94
-func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
95
-	var eopts exportOpts
96
-	for _, o := range opts {
97
-		if err := o(&eopts); err != nil {
98
-			return eopts, err
99
-		}
100
-	}
101
-	return eopts, nil
102
-}
103
-
104
-// Export exports an image to a Tar stream.
105
-// OCI format is used by default.
106
-// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
107
-// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
108
-func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
109
-	_, err := resolveExportOpt(opts...) // unused now
110
-	if err != nil {
111
-		return nil, err
112
-	}
113
-	pr, pw := io.Pipe()
114
-	go func() {
115
-		pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
116
-	}()
117
-	return pr, nil
118
-}
... ...
@@ -33,25 +33,14 @@ import (
33 33
 
34 34
 // Install a binary image into the opt service
35 35
 func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error {
36
-	resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
37
-		Filters: []string{
38
-			"id==opt",
39
-		},
40
-	})
41
-	if err != nil {
42
-		return err
43
-	}
44
-	if len(resp.Plugins) != 1 {
45
-		return errors.New("opt service not enabled")
46
-	}
47
-	path := resp.Plugins[0].Exports["path"]
48
-	if path == "" {
49
-		return errors.New("opt path not exported")
50
-	}
51 36
 	var config InstallConfig
52 37
 	for _, o := range opts {
53 38
 		o(&config)
54 39
 	}
40
+	path, err := c.getInstallPath(ctx, config)
41
+	if err != nil {
42
+		return err
43
+	}
55 44
 	var (
56 45
 		cs       = image.ContentStore()
57 46
 		platform = platforms.Default()
... ...
@@ -89,3 +78,25 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
89 89
 	}
90 90
 	return nil
91 91
 }
92
+
93
+func (c *Client) getInstallPath(ctx context.Context, config InstallConfig) (string, error) {
94
+	if config.Path != "" {
95
+		return config.Path, nil
96
+	}
97
+	resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
98
+		Filters: []string{
99
+			"id==opt",
100
+		},
101
+	})
102
+	if err != nil {
103
+		return "", err
104
+	}
105
+	if len(resp.Plugins) != 1 {
106
+		return "", errors.New("opt service not enabled")
107
+	}
108
+	path := resp.Plugins[0].Exports["path"]
109
+	if path == "" {
110
+		return "", errors.New("opt path not exported")
111
+	}
112
+	return path, nil
113
+}
... ...
@@ -25,6 +25,8 @@ type InstallConfig struct {
25 25
 	Libs bool
26 26
 	// Replace will overwrite existing binaries or libs in the opt directory
27 27
 	Replace bool
28
+	// Path to install libs and binaries to
29
+	Path string
28 30
 }
29 31
 
30 32
 // WithInstallLibs installs libs from the image
... ...
@@ -36,3 +38,10 @@ func WithInstallLibs(c *InstallConfig) {
36 36
 func WithInstallReplace(c *InstallConfig) {
37 37
 	c.Replace = true
38 38
 }
39
+
40
+// WithInstallPath sets the optional install path
41
+func WithInstallPath(path string) InstallOpts {
42
+	return func(c *InstallConfig) {
43
+		c.Path = path
44
+	}
45
+}
... ...
@@ -19,8 +19,8 @@ package metadata
19 19
 import (
20 20
 	"context"
21 21
 
22
-	"github.com/boltdb/bolt"
23 22
 	"github.com/pkg/errors"
23
+	bolt "go.etcd.io/bbolt"
24 24
 )
25 25
 
26 26
 type transactionKey struct{}
... ...
@@ -19,8 +19,8 @@ package boltutil
19 19
 import (
20 20
 	"time"
21 21
 
22
-	"github.com/boltdb/bolt"
23 22
 	"github.com/pkg/errors"
23
+	bolt "go.etcd.io/bbolt"
24 24
 )
25 25
 
26 26
 var (
... ...
@@ -17,11 +17,11 @@
17 17
 package metadata
18 18
 
19 19
 import (
20
-	"github.com/boltdb/bolt"
21 20
 	digest "github.com/opencontainers/go-digest"
21
+	bolt "go.etcd.io/bbolt"
22 22
 )
23 23
 
24
-// The layout where a "/" delineates a bucket is desribed in the following
24
+// The layout where a "/" delineates a bucket is described in the following
25 25
 // section. Please try to follow this as closely as possible when adding
26 26
 // functionality. We can bolster this with helpers and more structure if that
27 27
 // becomes an issue.
... ...
@@ -164,11 +164,11 @@ func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Buck
164 164
 }
165 165
 
166 166
 func createBlobBucket(tx *bolt.Tx, namespace string, dgst digest.Digest) (*bolt.Bucket, error) {
167
-	bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(dgst.String()))
167
+	bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob)
168 168
 	if err != nil {
169 169
 		return nil, err
170 170
 	}
171
-	return bkt, nil
171
+	return bkt.CreateBucket([]byte(dgst.String()))
172 172
 }
173 173
 
174 174
 func getBlobsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
... ...
@@ -21,7 +21,6 @@ import (
21 21
 	"strings"
22 22
 	"time"
23 23
 
24
-	"github.com/boltdb/bolt"
25 24
 	"github.com/containerd/containerd/containers"
26 25
 	"github.com/containerd/containerd/errdefs"
27 26
 	"github.com/containerd/containerd/filters"
... ...
@@ -32,6 +31,7 @@ import (
32 32
 	"github.com/gogo/protobuf/proto"
33 33
 	"github.com/gogo/protobuf/types"
34 34
 	"github.com/pkg/errors"
35
+	bolt "go.etcd.io/bbolt"
35 36
 )
36 37
 
37 38
 type containerStore struct {
... ...
@@ -23,7 +23,6 @@ import (
23 23
 	"sync"
24 24
 	"time"
25 25
 
26
-	"github.com/boltdb/bolt"
27 26
 	"github.com/containerd/containerd/content"
28 27
 	"github.com/containerd/containerd/errdefs"
29 28
 	"github.com/containerd/containerd/filters"
... ...
@@ -34,6 +33,7 @@ import (
34 34
 	digest "github.com/opencontainers/go-digest"
35 35
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
36 36
 	"github.com/pkg/errors"
37
+	bolt "go.etcd.io/bbolt"
37 38
 )
38 39
 
39 40
 type contentStore struct {
... ...
@@ -592,9 +592,6 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
592 592
 		}
593 593
 		size = nw.desc.Size
594 594
 		actual = nw.desc.Digest
595
-		if getBlobBucket(tx, nw.namespace, actual) != nil {
596
-			return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
597
-		}
598 595
 	} else {
599 596
 		status, err := nw.w.Status()
600 597
 		if err != nil {
... ...
@@ -606,18 +603,16 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
606 606
 		size = status.Offset
607 607
 		actual = nw.w.Digest()
608 608
 
609
-		if err := nw.w.Commit(ctx, size, expected); err != nil {
610
-			if !errdefs.IsAlreadyExists(err) {
611
-				return "", err
612
-			}
613
-			if getBlobBucket(tx, nw.namespace, actual) != nil {
614
-				return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
615
-			}
609
+		if err := nw.w.Commit(ctx, size, expected); err != nil && !errdefs.IsAlreadyExists(err) {
610
+			return "", err
616 611
 		}
617 612
 	}
618 613
 
619 614
 	bkt, err := createBlobBucket(tx, nw.namespace, actual)
620 615
 	if err != nil {
616
+		if err == bolt.ErrBucketExists {
617
+			return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
618
+		}
621 619
 		return "", err
622 620
 	}
623 621
 
... ...
@@ -23,12 +23,12 @@ import (
23 23
 	"sync"
24 24
 	"time"
25 25
 
26
-	"github.com/boltdb/bolt"
27 26
 	"github.com/containerd/containerd/content"
28 27
 	"github.com/containerd/containerd/gc"
29 28
 	"github.com/containerd/containerd/log"
30 29
 	"github.com/containerd/containerd/snapshots"
31 30
 	"github.com/pkg/errors"
31
+	bolt "go.etcd.io/bbolt"
32 32
 )
33 33
 
34 34
 const (
... ...
@@ -43,7 +43,7 @@ const (
43 43
 	// dbVersion represents updates to the schema
44 44
 	// version which are additions and compatible with
45 45
 	// prior version of the same schema.
46
-	dbVersion = 2
46
+	dbVersion = 3
47 47
 )
48 48
 
49 49
 // DB represents a metadata database backed by a bolt
... ...
@@ -23,10 +23,10 @@ import (
23 23
 	"strings"
24 24
 	"time"
25 25
 
26
-	"github.com/boltdb/bolt"
27 26
 	"github.com/containerd/containerd/gc"
28 27
 	"github.com/containerd/containerd/log"
29 28
 	"github.com/pkg/errors"
29
+	bolt "go.etcd.io/bbolt"
30 30
 )
31 31
 
32 32
 const (
... ...
@@ -23,7 +23,6 @@ import (
23 23
 	"strings"
24 24
 	"time"
25 25
 
26
-	"github.com/boltdb/bolt"
27 26
 	"github.com/containerd/containerd/errdefs"
28 27
 	"github.com/containerd/containerd/filters"
29 28
 	"github.com/containerd/containerd/images"
... ...
@@ -33,6 +32,7 @@ import (
33 33
 	digest "github.com/opencontainers/go-digest"
34 34
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
35 35
 	"github.com/pkg/errors"
36
+	bolt "go.etcd.io/bbolt"
36 37
 )
37 38
 
38 39
 type imageStore struct {
... ...
@@ -20,7 +20,6 @@ import (
20 20
 	"context"
21 21
 	"time"
22 22
 
23
-	"github.com/boltdb/bolt"
24 23
 	"github.com/containerd/containerd/errdefs"
25 24
 	"github.com/containerd/containerd/filters"
26 25
 	"github.com/containerd/containerd/leases"
... ...
@@ -28,6 +27,7 @@ import (
28 28
 	"github.com/containerd/containerd/namespaces"
29 29
 	digest "github.com/opencontainers/go-digest"
30 30
 	"github.com/pkg/errors"
31
+	bolt "go.etcd.io/bbolt"
31 32
 )
32 33
 
33 34
 // LeaseManager manages the create/delete lifecyle of leases
... ...
@@ -16,7 +16,7 @@
16 16
 
17 17
 package metadata
18 18
 
19
-import "github.com/boltdb/bolt"
19
+import bolt "go.etcd.io/bbolt"
20 20
 
21 21
 type migration struct {
22 22
 	schema  string
... ...
@@ -45,6 +45,11 @@ var migrations = []migration{
45 45
 		version: 2,
46 46
 		migrate: migrateIngests,
47 47
 	},
48
+	{
49
+		schema:  "v1",
50
+		version: 3,
51
+		migrate: noOpMigration,
52
+	},
48 53
 }
49 54
 
50 55
 // addChildLinks Adds children key to the snapshotters to enforce snapshot
... ...
@@ -154,3 +159,10 @@ func migrateIngests(tx *bolt.Tx) error {
154 154
 
155 155
 	return nil
156 156
 }
157
+
158
+// noOpMigration was for a database change from boltdb/bolt which is no
159
+// longer being supported, to go.etcd.io/bbolt which is the currently
160
+// maintained repo for boltdb.
161
+func noOpMigration(tx *bolt.Tx) error {
162
+	return nil
163
+}
... ...
@@ -19,11 +19,11 @@ package metadata
19 19
 import (
20 20
 	"context"
21 21
 
22
-	"github.com/boltdb/bolt"
23 22
 	"github.com/containerd/containerd/errdefs"
24 23
 	l "github.com/containerd/containerd/labels"
25 24
 	"github.com/containerd/containerd/namespaces"
26 25
 	"github.com/pkg/errors"
26
+	bolt "go.etcd.io/bbolt"
27 27
 )
28 28
 
29 29
 type namespaceStore struct {
... ...
@@ -23,7 +23,6 @@ import (
23 23
 	"sync"
24 24
 	"time"
25 25
 
26
-	"github.com/boltdb/bolt"
27 26
 	"github.com/containerd/containerd/errdefs"
28 27
 	"github.com/containerd/containerd/labels"
29 28
 	"github.com/containerd/containerd/log"
... ...
@@ -32,6 +31,7 @@ import (
32 32
 	"github.com/containerd/containerd/namespaces"
33 33
 	"github.com/containerd/containerd/snapshots"
34 34
 	"github.com/pkg/errors"
35
+	bolt "go.etcd.io/bbolt"
35 36
 )
36 37
 
37 38
 type snapshotter struct {
... ...
@@ -32,6 +32,10 @@ var (
32 32
 
33 33
 // Mount to the provided target
34 34
 func (m *Mount) Mount(target string) error {
35
+	if m.Type != "windows-layer" {
36
+		return errors.Errorf("invalid windows mount type: '%s'", m.Type)
37
+	}
38
+
35 39
 	home, layerID := filepath.Split(m.Source)
36 40
 
37 41
 	parentLayerPaths, err := m.GetParentPaths()
... ...
@@ -18,11 +18,27 @@ package oci
18 18
 
19 19
 import (
20 20
 	"context"
21
+	"path/filepath"
22
+	"runtime"
23
+
24
+	"github.com/containerd/containerd/namespaces"
25
+	"github.com/containerd/containerd/platforms"
21 26
 
22 27
 	"github.com/containerd/containerd/containers"
23 28
 	specs "github.com/opencontainers/runtime-spec/specs-go"
24 29
 )
25 30
 
31
+const (
32
+	rwm               = "rwm"
33
+	defaultRootfsPath = "rootfs"
34
+)
35
+
36
+var (
37
+	defaultUnixEnv = []string{
38
+		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
39
+	}
40
+)
41
+
26 42
 // Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
27 43
 // to be created without the "issues" with go vendoring and package imports
28 44
 type Spec = specs.Spec
... ...
@@ -30,12 +46,36 @@ type Spec = specs.Spec
30 30
 // GenerateSpec will generate a default spec from the provided image
31 31
 // for use as a containerd container
32 32
 func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
33
-	s, err := createDefaultSpec(ctx, c.ID)
34
-	if err != nil {
33
+	return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
34
+}
35
+
36
+// GenerateSpecWithPlatform will generate a default spec from the provided image
37
+// for use as a containerd container in the platform requested.
38
+func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
39
+	var s Spec
40
+	if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
35 41
 		return nil, err
36 42
 	}
37 43
 
38
-	return s, ApplyOpts(ctx, client, c, s, opts...)
44
+	return &s, ApplyOpts(ctx, client, c, &s, opts...)
45
+}
46
+
47
+func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
48
+	plat, err := platforms.Parse(platform)
49
+	if err != nil {
50
+		return err
51
+	}
52
+
53
+	if plat.OS == "windows" {
54
+		err = populateDefaultWindowsSpec(ctx, s, id)
55
+	} else {
56
+		err = populateDefaultUnixSpec(ctx, s, id)
57
+		if err == nil && runtime.GOOS == "windows" {
58
+			// To run LCOW we have a Linux and Windows section. Add an empty one now.
59
+			s.Windows = &specs.Windows{}
60
+		}
61
+	}
62
+	return err
39 63
 }
40 64
 
41 65
 // ApplyOpts applys the options to the given spec, injecting data from the
... ...
@@ -50,7 +90,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S
50 50
 	return nil
51 51
 }
52 52
 
53
-func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
54
-	var s Spec
55
-	return &s, populateDefaultSpec(ctx, &s, id)
53
+func defaultUnixCaps() []string {
54
+	return []string{
55
+		"CAP_CHOWN",
56
+		"CAP_DAC_OVERRIDE",
57
+		"CAP_FSETID",
58
+		"CAP_FOWNER",
59
+		"CAP_MKNOD",
60
+		"CAP_NET_RAW",
61
+		"CAP_SETGID",
62
+		"CAP_SETUID",
63
+		"CAP_SETFCAP",
64
+		"CAP_SETPCAP",
65
+		"CAP_NET_BIND_SERVICE",
66
+		"CAP_SYS_CHROOT",
67
+		"CAP_KILL",
68
+		"CAP_AUDIT_WRITE",
69
+	}
70
+}
71
+
72
+func defaultUnixNamespaces() []specs.LinuxNamespace {
73
+	return []specs.LinuxNamespace{
74
+		{
75
+			Type: specs.PIDNamespace,
76
+		},
77
+		{
78
+			Type: specs.IPCNamespace,
79
+		},
80
+		{
81
+			Type: specs.UTSNamespace,
82
+		},
83
+		{
84
+			Type: specs.MountNamespace,
85
+		},
86
+		{
87
+			Type: specs.NetworkNamespace,
88
+		},
89
+	}
90
+}
91
+
92
+func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
93
+	ns, err := namespaces.NamespaceRequired(ctx)
94
+	if err != nil {
95
+		return err
96
+	}
97
+
98
+	*s = Spec{
99
+		Version: specs.Version,
100
+		Root: &specs.Root{
101
+			Path: defaultRootfsPath,
102
+		},
103
+		Process: &specs.Process{
104
+			Env:             defaultUnixEnv,
105
+			Cwd:             "/",
106
+			NoNewPrivileges: true,
107
+			User: specs.User{
108
+				UID: 0,
109
+				GID: 0,
110
+			},
111
+			Capabilities: &specs.LinuxCapabilities{
112
+				Bounding:    defaultUnixCaps(),
113
+				Permitted:   defaultUnixCaps(),
114
+				Inheritable: defaultUnixCaps(),
115
+				Effective:   defaultUnixCaps(),
116
+			},
117
+			Rlimits: []specs.POSIXRlimit{
118
+				{
119
+					Type: "RLIMIT_NOFILE",
120
+					Hard: uint64(1024),
121
+					Soft: uint64(1024),
122
+				},
123
+			},
124
+		},
125
+		Mounts: []specs.Mount{
126
+			{
127
+				Destination: "/proc",
128
+				Type:        "proc",
129
+				Source:      "proc",
130
+			},
131
+			{
132
+				Destination: "/dev",
133
+				Type:        "tmpfs",
134
+				Source:      "tmpfs",
135
+				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
136
+			},
137
+			{
138
+				Destination: "/dev/pts",
139
+				Type:        "devpts",
140
+				Source:      "devpts",
141
+				Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
142
+			},
143
+			{
144
+				Destination: "/dev/shm",
145
+				Type:        "tmpfs",
146
+				Source:      "shm",
147
+				Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
148
+			},
149
+			{
150
+				Destination: "/dev/mqueue",
151
+				Type:        "mqueue",
152
+				Source:      "mqueue",
153
+				Options:     []string{"nosuid", "noexec", "nodev"},
154
+			},
155
+			{
156
+				Destination: "/sys",
157
+				Type:        "sysfs",
158
+				Source:      "sysfs",
159
+				Options:     []string{"nosuid", "noexec", "nodev", "ro"},
160
+			},
161
+			{
162
+				Destination: "/run",
163
+				Type:        "tmpfs",
164
+				Source:      "tmpfs",
165
+				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
166
+			},
167
+		},
168
+		Linux: &specs.Linux{
169
+			MaskedPaths: []string{
170
+				"/proc/acpi",
171
+				"/proc/kcore",
172
+				"/proc/keys",
173
+				"/proc/latency_stats",
174
+				"/proc/timer_list",
175
+				"/proc/timer_stats",
176
+				"/proc/sched_debug",
177
+				"/sys/firmware",
178
+				"/proc/scsi",
179
+			},
180
+			ReadonlyPaths: []string{
181
+				"/proc/asound",
182
+				"/proc/bus",
183
+				"/proc/fs",
184
+				"/proc/irq",
185
+				"/proc/sys",
186
+				"/proc/sysrq-trigger",
187
+			},
188
+			CgroupsPath: filepath.Join("/", ns, id),
189
+			Resources: &specs.LinuxResources{
190
+				Devices: []specs.LinuxDeviceCgroup{
191
+					{
192
+						Allow:  false,
193
+						Access: rwm,
194
+					},
195
+				},
196
+			},
197
+			Namespaces: defaultUnixNamespaces(),
198
+		},
199
+	}
200
+	return nil
201
+}
202
+
203
+func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
204
+	*s = Spec{
205
+		Version: specs.Version,
206
+		Root:    &specs.Root{},
207
+		Process: &specs.Process{
208
+			Cwd: `C:\`,
209
+			ConsoleSize: &specs.Box{
210
+				Width:  80,
211
+				Height: 20,
212
+			},
213
+		},
214
+		Windows: &specs.Windows{
215
+			IgnoreFlushesDuringBoot: true,
216
+			Network: &specs.WindowsNetwork{
217
+				AllowUnqualifiedDNSQuery: true,
218
+			},
219
+		},
220
+	}
221
+	return nil
56 222
 }
... ...
@@ -19,12 +19,25 @@ package oci
19 19
 import (
20 20
 	"context"
21 21
 	"encoding/json"
22
+	"fmt"
22 23
 	"io/ioutil"
24
+	"os"
25
+	"path/filepath"
26
+	"strconv"
23 27
 	"strings"
24 28
 
25 29
 	"github.com/containerd/containerd/containers"
30
+	"github.com/containerd/containerd/content"
31
+	"github.com/containerd/containerd/images"
32
+	"github.com/containerd/containerd/mount"
33
+	"github.com/containerd/containerd/namespaces"
34
+	"github.com/containerd/containerd/platforms"
35
+	"github.com/containerd/continuity/fs"
36
+	"github.com/opencontainers/image-spec/specs-go/v1"
37
+	"github.com/opencontainers/runc/libcontainer/user"
26 38
 	specs "github.com/opencontainers/runtime-spec/specs-go"
27 39
 	"github.com/pkg/errors"
40
+	"github.com/syndtr/gocapability/capability"
28 41
 )
29 42
 
30 43
 // SpecOpts sets spec specific information to a newly generated OCI spec
... ...
@@ -49,13 +62,45 @@ func setProcess(s *Spec) {
49 49
 	}
50 50
 }
51 51
 
52
+// setRoot sets Root to empty if unset
53
+func setRoot(s *Spec) {
54
+	if s.Root == nil {
55
+		s.Root = &specs.Root{}
56
+	}
57
+}
58
+
59
+// setLinux sets Linux to empty if unset
60
+func setLinux(s *Spec) {
61
+	if s.Linux == nil {
62
+		s.Linux = &specs.Linux{}
63
+	}
64
+}
65
+
66
+// setCapabilities sets Linux Capabilities to empty if unset
67
+func setCapabilities(s *Spec) {
68
+	setProcess(s)
69
+	if s.Process.Capabilities == nil {
70
+		s.Process.Capabilities = &specs.LinuxCapabilities{}
71
+	}
72
+}
73
+
52 74
 // WithDefaultSpec returns a SpecOpts that will populate the spec with default
53 75
 // values.
54 76
 //
55 77
 // Use as the first option to clear the spec, then apply options afterwards.
56 78
 func WithDefaultSpec() SpecOpts {
57 79
 	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
58
-		return populateDefaultSpec(ctx, s, c.ID)
80
+		return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s)
81
+	}
82
+}
83
+
84
+// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec
85
+// with default values for a given platform.
86
+//
87
+// Use as the first option to clear the spec, then apply options afterwards.
88
+func WithDefaultSpecForPlatform(platform string) SpecOpts {
89
+	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
90
+		return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s)
59 91
 	}
60 92
 }
61 93
 
... ...
@@ -81,6 +126,55 @@ func WithSpecFromFile(filename string) SpecOpts {
81 81
 	}
82 82
 }
83 83
 
84
+// WithEnv appends environment variables
85
+func WithEnv(environmentVariables []string) SpecOpts {
86
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
87
+		if len(environmentVariables) > 0 {
88
+			setProcess(s)
89
+			s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables)
90
+		}
91
+		return nil
92
+	}
93
+}
94
+
95
+// replaceOrAppendEnvValues returns the defaults with the overrides either
96
+// replaced by env key or appended to the list
97
+func replaceOrAppendEnvValues(defaults, overrides []string) []string {
98
+	cache := make(map[string]int, len(defaults))
99
+	for i, e := range defaults {
100
+		parts := strings.SplitN(e, "=", 2)
101
+		cache[parts[0]] = i
102
+	}
103
+
104
+	for _, value := range overrides {
105
+		// Values w/o = means they want this env to be removed/unset.
106
+		if !strings.Contains(value, "=") {
107
+			if i, exists := cache[value]; exists {
108
+				defaults[i] = "" // Used to indicate it should be removed
109
+			}
110
+			continue
111
+		}
112
+
113
+		// Just do a normal set/update
114
+		parts := strings.SplitN(value, "=", 2)
115
+		if i, exists := cache[parts[0]]; exists {
116
+			defaults[i] = value
117
+		} else {
118
+			defaults = append(defaults, value)
119
+		}
120
+	}
121
+
122
+	// Now remove all entries that we want to "unset"
123
+	for i := 0; i < len(defaults); i++ {
124
+		if defaults[i] == "" {
125
+			defaults = append(defaults[:i], defaults[i+1:]...)
126
+			i--
127
+		}
128
+	}
129
+
130
+	return defaults
131
+}
132
+
84 133
 // WithProcessArgs replaces the args on the generated spec
85 134
 func WithProcessArgs(args ...string) SpecOpts {
86 135
 	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
... ...
@@ -99,6 +193,32 @@ func WithProcessCwd(cwd string) SpecOpts {
99 99
 	}
100 100
 }
101 101
 
102
+// WithTTY sets the information on the spec as well as the environment variables for
103
+// using a TTY
104
+func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
105
+	setProcess(s)
106
+	s.Process.Terminal = true
107
+	if s.Linux != nil {
108
+		s.Process.Env = append(s.Process.Env, "TERM=xterm")
109
+	}
110
+
111
+	return nil
112
+}
113
+
114
+// WithTTYSize sets the information on the spec as well as the environment variables for
115
+// using a TTY
116
+func WithTTYSize(width, height int) SpecOpts {
117
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
118
+		setProcess(s)
119
+		if s.Process.ConsoleSize == nil {
120
+			s.Process.ConsoleSize = &specs.Box{}
121
+		}
122
+		s.Process.ConsoleSize.Width = uint(width)
123
+		s.Process.ConsoleSize.Height = uint(height)
124
+		return nil
125
+	}
126
+}
127
+
102 128
 // WithHostname sets the container's hostname
103 129
 func WithHostname(name string) SpecOpts {
104 130
 	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
... ...
@@ -107,59 +227,779 @@ func WithHostname(name string) SpecOpts {
107 107
 	}
108 108
 }
109 109
 
110
-// WithEnv appends environment variables
111
-func WithEnv(environmentVariables []string) SpecOpts {
110
+// WithMounts appends mounts
111
+func WithMounts(mounts []specs.Mount) SpecOpts {
112 112
 	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
113
-		if len(environmentVariables) > 0 {
114
-			setProcess(s)
115
-			s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables)
113
+		s.Mounts = append(s.Mounts, mounts...)
114
+		return nil
115
+	}
116
+}
117
+
118
+// WithHostNamespace allows a task to run inside the host's linux namespace
119
+func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
120
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
121
+		setLinux(s)
122
+		for i, n := range s.Linux.Namespaces {
123
+			if n.Type == ns {
124
+				s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
125
+				return nil
126
+			}
116 127
 		}
117 128
 		return nil
118 129
 	}
119 130
 }
120 131
 
121
-// WithMounts appends mounts
122
-func WithMounts(mounts []specs.Mount) SpecOpts {
132
+// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
133
+// spec, the existing namespace is replaced by the one provided.
134
+func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
123 135
 	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
124
-		s.Mounts = append(s.Mounts, mounts...)
136
+		setLinux(s)
137
+		for i, n := range s.Linux.Namespaces {
138
+			if n.Type == ns.Type {
139
+				before := s.Linux.Namespaces[:i]
140
+				after := s.Linux.Namespaces[i+1:]
141
+				s.Linux.Namespaces = append(before, ns)
142
+				s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
143
+				return nil
144
+			}
145
+		}
146
+		s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
125 147
 		return nil
126 148
 	}
127 149
 }
128 150
 
129
-// replaceOrAppendEnvValues returns the defaults with the overrides either
130
-// replaced by env key or appended to the list
131
-func replaceOrAppendEnvValues(defaults, overrides []string) []string {
132
-	cache := make(map[string]int, len(defaults))
133
-	for i, e := range defaults {
134
-		parts := strings.SplitN(e, "=", 2)
135
-		cache[parts[0]] = i
151
+// WithImageConfig configures the spec to from the configuration of an Image
152
+func WithImageConfig(image Image) SpecOpts {
153
+	return WithImageConfigArgs(image, nil)
154
+}
155
+
156
+// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
157
+// replaces the CMD of the image
158
+func WithImageConfigArgs(image Image, args []string) SpecOpts {
159
+	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
160
+		ic, err := image.Config(ctx)
161
+		if err != nil {
162
+			return err
163
+		}
164
+		var (
165
+			ociimage v1.Image
166
+			config   v1.ImageConfig
167
+		)
168
+		switch ic.MediaType {
169
+		case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
170
+			p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
171
+			if err != nil {
172
+				return err
173
+			}
174
+
175
+			if err := json.Unmarshal(p, &ociimage); err != nil {
176
+				return err
177
+			}
178
+			config = ociimage.Config
179
+		default:
180
+			return fmt.Errorf("unknown image config media type %s", ic.MediaType)
181
+		}
182
+
183
+		setProcess(s)
184
+		if s.Linux != nil {
185
+			s.Process.Env = append(s.Process.Env, config.Env...)
186
+			cmd := config.Cmd
187
+			if len(args) > 0 {
188
+				cmd = args
189
+			}
190
+			s.Process.Args = append(config.Entrypoint, cmd...)
191
+
192
+			cwd := config.WorkingDir
193
+			if cwd == "" {
194
+				cwd = "/"
195
+			}
196
+			s.Process.Cwd = cwd
197
+			if config.User != "" {
198
+				if err := WithUser(config.User)(ctx, client, c, s); err != nil {
199
+					return err
200
+				}
201
+				return WithAdditionalGIDs(fmt.Sprintf("%d", s.Process.User.UID))(ctx, client, c, s)
202
+			}
203
+			// we should query the image's /etc/group for additional GIDs
204
+			// even if there is no specified user in the image config
205
+			return WithAdditionalGIDs("root")(ctx, client, c, s)
206
+		} else if s.Windows != nil {
207
+			s.Process.Env = config.Env
208
+			s.Process.Args = append(config.Entrypoint, config.Cmd...)
209
+			s.Process.User = specs.User{
210
+				Username: config.User,
211
+			}
212
+		} else {
213
+			return errors.New("spec does not contain Linux or Windows section")
214
+		}
215
+		return nil
136 216
 	}
217
+}
137 218
 
138
-	for _, value := range overrides {
139
-		// Values w/o = means they want this env to be removed/unset.
140
-		if !strings.Contains(value, "=") {
141
-			if i, exists := cache[value]; exists {
142
-				defaults[i] = "" // Used to indicate it should be removed
219
+// WithRootFSPath specifies unmanaged rootfs path.
220
+func WithRootFSPath(path string) SpecOpts {
221
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
222
+		setRoot(s)
223
+		s.Root.Path = path
224
+		// Entrypoint is not set here (it's up to caller)
225
+		return nil
226
+	}
227
+}
228
+
229
+// WithRootFSReadonly sets specs.Root.Readonly to true
230
+func WithRootFSReadonly() SpecOpts {
231
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
232
+		setRoot(s)
233
+		s.Root.Readonly = true
234
+		return nil
235
+	}
236
+}
237
+
238
+// WithNoNewPrivileges sets no_new_privileges on the process for the container
239
+func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
240
+	setProcess(s)
241
+	s.Process.NoNewPrivileges = true
242
+	return nil
243
+}
244
+
245
+// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
246
+func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
247
+	s.Mounts = append(s.Mounts, specs.Mount{
248
+		Destination: "/etc/hosts",
249
+		Type:        "bind",
250
+		Source:      "/etc/hosts",
251
+		Options:     []string{"rbind", "ro"},
252
+	})
253
+	return nil
254
+}
255
+
256
+// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
257
+func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
258
+	s.Mounts = append(s.Mounts, specs.Mount{
259
+		Destination: "/etc/resolv.conf",
260
+		Type:        "bind",
261
+		Source:      "/etc/resolv.conf",
262
+		Options:     []string{"rbind", "ro"},
263
+	})
264
+	return nil
265
+}
266
+
267
+// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
268
+func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
269
+	s.Mounts = append(s.Mounts, specs.Mount{
270
+		Destination: "/etc/localtime",
271
+		Type:        "bind",
272
+		Source:      "/etc/localtime",
273
+		Options:     []string{"rbind", "ro"},
274
+	})
275
+	return nil
276
+}
277
+
278
+// WithUserNamespace sets the uid and gid mappings for the task
279
+// this can be called multiple times to add more mappings to the generated spec
280
+func WithUserNamespace(container, host, size uint32) SpecOpts {
281
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
282
+		var hasUserns bool
283
+		setLinux(s)
284
+		for _, ns := range s.Linux.Namespaces {
285
+			if ns.Type == specs.UserNamespace {
286
+				hasUserns = true
287
+				break
143 288
 			}
144
-			continue
145 289
 		}
290
+		if !hasUserns {
291
+			s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
292
+				Type: specs.UserNamespace,
293
+			})
294
+		}
295
+		mapping := specs.LinuxIDMapping{
296
+			ContainerID: container,
297
+			HostID:      host,
298
+			Size:        size,
299
+		}
300
+		s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
301
+		s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
302
+		return nil
303
+	}
304
+}
146 305
 
147
-		// Just do a normal set/update
148
-		parts := strings.SplitN(value, "=", 2)
149
-		if i, exists := cache[parts[0]]; exists {
150
-			defaults[i] = value
306
+// WithCgroup sets the container's cgroup path
307
+func WithCgroup(path string) SpecOpts {
308
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
309
+		setLinux(s)
310
+		s.Linux.CgroupsPath = path
311
+		return nil
312
+	}
313
+}
314
+
315
+// WithNamespacedCgroup uses the namespace set on the context to create a
316
+// root directory for containers in the cgroup with the id as the subcgroup
317
+func WithNamespacedCgroup() SpecOpts {
318
+	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
319
+		namespace, err := namespaces.NamespaceRequired(ctx)
320
+		if err != nil {
321
+			return err
322
+		}
323
+		setLinux(s)
324
+		s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
325
+		return nil
326
+	}
327
+}
328
+
329
+// WithUser sets the user to be used within the container.
330
+// It accepts a valid user string in OCI Image Spec v1.0.0:
331
+//   user, uid, user:group, uid:gid, uid:group, user:gid
332
+func WithUser(userstr string) SpecOpts {
333
+	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
334
+		setProcess(s)
335
+		parts := strings.Split(userstr, ":")
336
+		switch len(parts) {
337
+		case 1:
338
+			v, err := strconv.Atoi(parts[0])
339
+			if err != nil {
340
+				// if we cannot parse as a uint they try to see if it is a username
341
+				return WithUsername(userstr)(ctx, client, c, s)
342
+			}
343
+			return WithUserID(uint32(v))(ctx, client, c, s)
344
+		case 2:
345
+			var (
346
+				username  string
347
+				groupname string
348
+			)
349
+			var uid, gid uint32
350
+			v, err := strconv.Atoi(parts[0])
351
+			if err != nil {
352
+				username = parts[0]
353
+			} else {
354
+				uid = uint32(v)
355
+			}
356
+			if v, err = strconv.Atoi(parts[1]); err != nil {
357
+				groupname = parts[1]
358
+			} else {
359
+				gid = uint32(v)
360
+			}
361
+			if username == "" && groupname == "" {
362
+				s.Process.User.UID, s.Process.User.GID = uid, gid
363
+				return nil
364
+			}
365
+			f := func(root string) error {
366
+				if username != "" {
367
+					user, err := getUserFromPath(root, func(u user.User) bool {
368
+						return u.Name == username
369
+					})
370
+					if err != nil {
371
+						return err
372
+					}
373
+					uid = uint32(user.Uid)
374
+				}
375
+				if groupname != "" {
376
+					gid, err = getGIDFromPath(root, func(g user.Group) bool {
377
+						return g.Name == groupname
378
+					})
379
+					if err != nil {
380
+						return err
381
+					}
382
+				}
383
+				s.Process.User.UID, s.Process.User.GID = uid, gid
384
+				return nil
385
+			}
386
+			if c.Snapshotter == "" && c.SnapshotKey == "" {
387
+				if !isRootfsAbs(s.Root.Path) {
388
+					return errors.New("rootfs absolute path is required")
389
+				}
390
+				return f(s.Root.Path)
391
+			}
392
+			if c.Snapshotter == "" {
393
+				return errors.New("no snapshotter set for container")
394
+			}
395
+			if c.SnapshotKey == "" {
396
+				return errors.New("rootfs snapshot not created for container")
397
+			}
398
+			snapshotter := client.SnapshotService(c.Snapshotter)
399
+			mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
400
+			if err != nil {
401
+				return err
402
+			}
403
+			return mount.WithTempMount(ctx, mounts, f)
404
+		default:
405
+			return fmt.Errorf("invalid USER value %s", userstr)
406
+		}
407
+	}
408
+}
409
+
410
+// WithUIDGID allows the UID and GID for the Process to be set
411
+func WithUIDGID(uid, gid uint32) SpecOpts {
412
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
413
+		setProcess(s)
414
+		s.Process.User.UID = uid
415
+		s.Process.User.GID = gid
416
+		return nil
417
+	}
418
+}
419
+
420
+// WithUserID sets the correct UID and GID for the container based
421
+// on the image's /etc/passwd contents. If /etc/passwd does not exist,
422
+// or uid is not found in /etc/passwd, it sets the requested uid,
423
+// additionally sets the gid to 0, and does not return an error.
424
+func WithUserID(uid uint32) SpecOpts {
425
+	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
426
+		setProcess(s)
427
+		if c.Snapshotter == "" && c.SnapshotKey == "" {
428
+			if !isRootfsAbs(s.Root.Path) {
429
+				return errors.Errorf("rootfs absolute path is required")
430
+			}
431
+			user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
432
+				return u.Uid == int(uid)
433
+			})
434
+			if err != nil {
435
+				if os.IsNotExist(err) || err == errNoUsersFound {
436
+					s.Process.User.UID, s.Process.User.GID = uid, 0
437
+					return nil
438
+				}
439
+				return err
440
+			}
441
+			s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
442
+			return nil
443
+
444
+		}
445
+		if c.Snapshotter == "" {
446
+			return errors.Errorf("no snapshotter set for container")
447
+		}
448
+		if c.SnapshotKey == "" {
449
+			return errors.Errorf("rootfs snapshot not created for container")
450
+		}
451
+		snapshotter := client.SnapshotService(c.Snapshotter)
452
+		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
453
+		if err != nil {
454
+			return err
455
+		}
456
+		return mount.WithTempMount(ctx, mounts, func(root string) error {
457
+			user, err := getUserFromPath(root, func(u user.User) bool {
458
+				return u.Uid == int(uid)
459
+			})
460
+			if err != nil {
461
+				if os.IsNotExist(err) || err == errNoUsersFound {
462
+					s.Process.User.UID, s.Process.User.GID = uid, 0
463
+					return nil
464
+				}
465
+				return err
466
+			}
467
+			s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
468
+			return nil
469
+		})
470
+	}
471
+}
472
+
473
+// WithUsername sets the correct UID and GID for the container
474
+// based on the the image's /etc/passwd contents. If /etc/passwd
475
+// does not exist, or the username is not found in /etc/passwd,
476
+// it returns error.
477
+func WithUsername(username string) SpecOpts {
478
+	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
479
+		setProcess(s)
480
+		if s.Linux != nil {
481
+			if c.Snapshotter == "" && c.SnapshotKey == "" {
482
+				if !isRootfsAbs(s.Root.Path) {
483
+					return errors.Errorf("rootfs absolute path is required")
484
+				}
485
+				user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
486
+					return u.Name == username
487
+				})
488
+				if err != nil {
489
+					return err
490
+				}
491
+				s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
492
+				return nil
493
+			}
494
+			if c.Snapshotter == "" {
495
+				return errors.Errorf("no snapshotter set for container")
496
+			}
497
+			if c.SnapshotKey == "" {
498
+				return errors.Errorf("rootfs snapshot not created for container")
499
+			}
500
+			snapshotter := client.SnapshotService(c.Snapshotter)
501
+			mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
502
+			if err != nil {
503
+				return err
504
+			}
505
+			return mount.WithTempMount(ctx, mounts, func(root string) error {
506
+				user, err := getUserFromPath(root, func(u user.User) bool {
507
+					return u.Name == username
508
+				})
509
+				if err != nil {
510
+					return err
511
+				}
512
+				s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
513
+				return nil
514
+			})
515
+		} else if s.Windows != nil {
516
+			s.Process.User.Username = username
151 517
 		} else {
152
-			defaults = append(defaults, value)
518
+			return errors.New("spec does not contain Linux or Windows section")
153 519
 		}
520
+		return nil
154 521
 	}
522
+}
155 523
 
156
-	// Now remove all entries that we want to "unset"
157
-	for i := 0; i < len(defaults); i++ {
158
-		if defaults[i] == "" {
159
-			defaults = append(defaults[:i], defaults[i+1:]...)
160
-			i--
524
+// WithAdditionalGIDs sets the OCI spec's additionalGids array to any additional groups listed
525
+// for a particular user in the /etc/groups file of the image's root filesystem
526
+// The passed in user can be either a uid or a username.
527
+func WithAdditionalGIDs(userstr string) SpecOpts {
528
+	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
529
+		setProcess(s)
530
+		setAdditionalGids := func(root string) error {
531
+			var username string
532
+			uid, err := strconv.Atoi(userstr)
533
+			if err == nil {
534
+				user, err := getUserFromPath(root, func(u user.User) bool {
535
+					return u.Uid == uid
536
+				})
537
+				if err != nil {
538
+					if os.IsNotExist(err) || err == errNoUsersFound {
539
+						return nil
540
+					}
541
+					return err
542
+				}
543
+				username = user.Name
544
+			} else {
545
+				username = userstr
546
+			}
547
+			gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool {
548
+				// we only want supplemental groups
549
+				if g.Name == username {
550
+					return false
551
+				}
552
+				for _, entry := range g.List {
553
+					if entry == username {
554
+						return true
555
+					}
556
+				}
557
+				return false
558
+			})
559
+			if err != nil {
560
+				if os.IsNotExist(err) {
561
+					return nil
562
+				}
563
+				return err
564
+			}
565
+			s.Process.User.AdditionalGids = gids
566
+			return nil
567
+		}
568
+		if c.Snapshotter == "" && c.SnapshotKey == "" {
569
+			if !isRootfsAbs(s.Root.Path) {
570
+				return errors.Errorf("rootfs absolute path is required")
571
+			}
572
+			return setAdditionalGids(s.Root.Path)
161 573
 		}
574
+		if c.Snapshotter == "" {
575
+			return errors.Errorf("no snapshotter set for container")
576
+		}
577
+		if c.SnapshotKey == "" {
578
+			return errors.Errorf("rootfs snapshot not created for container")
579
+		}
580
+		snapshotter := client.SnapshotService(c.Snapshotter)
581
+		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
582
+		if err != nil {
583
+			return err
584
+		}
585
+		return mount.WithTempMount(ctx, mounts, setAdditionalGids)
162 586
 	}
587
+}
163 588
 
164
-	return defaults
589
+// WithCapabilities sets Linux capabilities on the process
590
+func WithCapabilities(caps []string) SpecOpts {
591
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
592
+		setCapabilities(s)
593
+
594
+		s.Process.Capabilities.Bounding = caps
595
+		s.Process.Capabilities.Effective = caps
596
+		s.Process.Capabilities.Permitted = caps
597
+		s.Process.Capabilities.Inheritable = caps
598
+
599
+		return nil
600
+	}
601
+}
602
+
603
+// WithAllCapabilities sets all linux capabilities for the process
604
+var WithAllCapabilities = WithCapabilities(getAllCapabilities())
605
+
606
+func getAllCapabilities() []string {
607
+	last := capability.CAP_LAST_CAP
608
+	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
609
+	if last == capability.Cap(63) {
610
+		last = capability.CAP_BLOCK_SUSPEND
611
+	}
612
+	var caps []string
613
+	for _, cap := range capability.List() {
614
+		if cap > last {
615
+			continue
616
+		}
617
+		caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
618
+	}
619
+	return caps
620
+}
621
+
622
+// WithAmbientCapabilities set the Linux ambient capabilities for the process
623
+// Ambient capabilities should only be set for non-root users or the caller should
624
+// understand how these capabilities are used and set
625
+func WithAmbientCapabilities(caps []string) SpecOpts {
626
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
627
+		setCapabilities(s)
628
+
629
+		s.Process.Capabilities.Ambient = caps
630
+		return nil
631
+	}
632
+}
633
+
634
+var errNoUsersFound = errors.New("no users found")
635
+
636
+func getUserFromPath(root string, filter func(user.User) bool) (user.User, error) {
637
+	ppath, err := fs.RootPath(root, "/etc/passwd")
638
+	if err != nil {
639
+		return user.User{}, err
640
+	}
641
+	users, err := user.ParsePasswdFileFilter(ppath, filter)
642
+	if err != nil {
643
+		return user.User{}, err
644
+	}
645
+	if len(users) == 0 {
646
+		return user.User{}, errNoUsersFound
647
+	}
648
+	return users[0], nil
649
+}
650
+
651
+var errNoGroupsFound = errors.New("no groups found")
652
+
653
+func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
654
+	gpath, err := fs.RootPath(root, "/etc/group")
655
+	if err != nil {
656
+		return 0, err
657
+	}
658
+	groups, err := user.ParseGroupFileFilter(gpath, filter)
659
+	if err != nil {
660
+		return 0, err
661
+	}
662
+	if len(groups) == 0 {
663
+		return 0, errNoGroupsFound
664
+	}
665
+	g := groups[0]
666
+	return uint32(g.Gid), nil
667
+}
668
+
669
+func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([]uint32, error) {
670
+	gpath, err := fs.RootPath(root, "/etc/group")
671
+	if err != nil {
672
+		return []uint32{}, err
673
+	}
674
+	groups, err := user.ParseGroupFileFilter(gpath, filter)
675
+	if err != nil {
676
+		return []uint32{}, err
677
+	}
678
+	if len(groups) == 0 {
679
+		// if there are no additional groups; just return an empty set
680
+		return []uint32{}, nil
681
+	}
682
+	addlGids := []uint32{}
683
+	for _, grp := range groups {
684
+		addlGids = append(addlGids, uint32(grp.Gid))
685
+	}
686
+	return addlGids, nil
687
+}
688
+
689
+func isRootfsAbs(root string) bool {
690
+	return filepath.IsAbs(root)
691
+}
692
+
693
+// WithMaskedPaths sets the masked paths option
694
+func WithMaskedPaths(paths []string) SpecOpts {
695
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
696
+		setLinux(s)
697
+		s.Linux.MaskedPaths = paths
698
+		return nil
699
+	}
700
+}
701
+
702
+// WithReadonlyPaths sets the read only paths option
703
+func WithReadonlyPaths(paths []string) SpecOpts {
704
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
705
+		setLinux(s)
706
+		s.Linux.ReadonlyPaths = paths
707
+		return nil
708
+	}
709
+}
710
+
711
+// WithWriteableSysfs makes any sysfs mounts writeable
712
+func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
713
+	for i, m := range s.Mounts {
714
+		if m.Type == "sysfs" {
715
+			var options []string
716
+			for _, o := range m.Options {
717
+				if o == "ro" {
718
+					o = "rw"
719
+				}
720
+				options = append(options, o)
721
+			}
722
+			s.Mounts[i].Options = options
723
+		}
724
+	}
725
+	return nil
726
+}
727
+
728
+// WithWriteableCgroupfs makes any cgroup mounts writeable
729
+func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
730
+	for i, m := range s.Mounts {
731
+		if m.Type == "cgroup" {
732
+			var options []string
733
+			for _, o := range m.Options {
734
+				if o == "ro" {
735
+					o = "rw"
736
+				}
737
+				options = append(options, o)
738
+			}
739
+			s.Mounts[i].Options = options
740
+		}
741
+	}
742
+	return nil
165 743
 }
744
+
745
+// WithSelinuxLabel sets the process SELinux label
746
+func WithSelinuxLabel(label string) SpecOpts {
747
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
748
+		setProcess(s)
749
+		s.Process.SelinuxLabel = label
750
+		return nil
751
+	}
752
+}
753
+
754
+// WithApparmorProfile sets the Apparmor profile for the process
755
+func WithApparmorProfile(profile string) SpecOpts {
756
+	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
757
+		setProcess(s)
758
+		s.Process.ApparmorProfile = profile
759
+		return nil
760
+	}
761
+}
762
+
763
+// WithSeccompUnconfined clears the seccomp profile
764
+func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
765
+	setLinux(s)
766
+	s.Linux.Seccomp = nil
767
+	return nil
768
+}
769
+
770
+// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
771
+// allowed and denied devices
772
+func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
773
+	setLinux(s)
774
+	if s.Linux.Resources == nil {
775
+		s.Linux.Resources = &specs.LinuxResources{}
776
+	}
777
+	s.Linux.Resources.Devices = nil
778
+	return nil
779
+}
780
+
781
+// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
782
+// the container's resource cgroup spec
783
+func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
784
+	setLinux(s)
785
+	if s.Linux.Resources == nil {
786
+		s.Linux.Resources = &specs.LinuxResources{}
787
+	}
788
+	intptr := func(i int64) *int64 {
789
+		return &i
790
+	}
791
+	s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
792
+		{
793
+			// "/dev/null",
794
+			Type:   "c",
795
+			Major:  intptr(1),
796
+			Minor:  intptr(3),
797
+			Access: rwm,
798
+			Allow:  true,
799
+		},
800
+		{
801
+			// "/dev/random",
802
+			Type:   "c",
803
+			Major:  intptr(1),
804
+			Minor:  intptr(8),
805
+			Access: rwm,
806
+			Allow:  true,
807
+		},
808
+		{
809
+			// "/dev/full",
810
+			Type:   "c",
811
+			Major:  intptr(1),
812
+			Minor:  intptr(7),
813
+			Access: rwm,
814
+			Allow:  true,
815
+		},
816
+		{
817
+			// "/dev/tty",
818
+			Type:   "c",
819
+			Major:  intptr(5),
820
+			Minor:  intptr(0),
821
+			Access: rwm,
822
+			Allow:  true,
823
+		},
824
+		{
825
+			// "/dev/zero",
826
+			Type:   "c",
827
+			Major:  intptr(1),
828
+			Minor:  intptr(5),
829
+			Access: rwm,
830
+			Allow:  true,
831
+		},
832
+		{
833
+			// "/dev/urandom",
834
+			Type:   "c",
835
+			Major:  intptr(1),
836
+			Minor:  intptr(9),
837
+			Access: rwm,
838
+			Allow:  true,
839
+		},
840
+		{
841
+			// "/dev/console",
842
+			Type:   "c",
843
+			Major:  intptr(5),
844
+			Minor:  intptr(1),
845
+			Access: rwm,
846
+			Allow:  true,
847
+		},
848
+		// /dev/pts/ - pts namespaces are "coming soon"
849
+		{
850
+			Type:   "c",
851
+			Major:  intptr(136),
852
+			Access: rwm,
853
+			Allow:  true,
854
+		},
855
+		{
856
+			Type:   "c",
857
+			Major:  intptr(5),
858
+			Minor:  intptr(2),
859
+			Access: rwm,
860
+			Allow:  true,
861
+		},
862
+		{
863
+			// tuntap
864
+			Type:   "c",
865
+			Major:  intptr(10),
866
+			Minor:  intptr(200),
867
+			Access: rwm,
868
+			Allow:  true,
869
+		},
870
+	}...)
871
+	return nil
872
+}
873
+
874
+// WithPrivileged sets up options for a privileged container
875
+// TODO(justincormack) device handling
876
+var WithPrivileged = Compose(
877
+	WithAllCapabilities,
878
+	WithMaskedPaths(nil),
879
+	WithReadonlyPaths(nil),
880
+	WithWriteableSysfs,
881
+	WithWriteableCgroupfs,
882
+	WithSelinuxLabel(""),
883
+	WithApparmorProfile(""),
884
+	WithSeccompUnconfined,
885
+)
166 886
deleted file mode 100644
... ...
@@ -1,733 +0,0 @@
1
-// +build !windows
2
-
3
-/*
4
-   Copyright The containerd Authors.
5
-
6
-   Licensed under the Apache License, Version 2.0 (the "License");
7
-   you may not use this file except in compliance with the License.
8
-   You may obtain a copy of the License at
9
-
10
-       http://www.apache.org/licenses/LICENSE-2.0
11
-
12
-   Unless required by applicable law or agreed to in writing, software
13
-   distributed under the License is distributed on an "AS IS" BASIS,
14
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
-   See the License for the specific language governing permissions and
16
-   limitations under the License.
17
-*/
18
-
19
-package oci
20
-
21
-import (
22
-	"context"
23
-	"encoding/json"
24
-	"fmt"
25
-	"os"
26
-	"path/filepath"
27
-	"strconv"
28
-	"strings"
29
-
30
-	"github.com/containerd/containerd/containers"
31
-	"github.com/containerd/containerd/content"
32
-	"github.com/containerd/containerd/images"
33
-	"github.com/containerd/containerd/mount"
34
-	"github.com/containerd/containerd/namespaces"
35
-	"github.com/containerd/continuity/fs"
36
-	"github.com/opencontainers/image-spec/specs-go/v1"
37
-	"github.com/opencontainers/runc/libcontainer/user"
38
-	specs "github.com/opencontainers/runtime-spec/specs-go"
39
-	"github.com/pkg/errors"
40
-	"github.com/syndtr/gocapability/capability"
41
-)
42
-
43
-// WithTTY sets the information on the spec as well as the environment variables for
44
-// using a TTY
45
-func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
46
-	setProcess(s)
47
-	s.Process.Terminal = true
48
-	s.Process.Env = append(s.Process.Env, "TERM=xterm")
49
-	return nil
50
-}
51
-
52
-// setRoot sets Root to empty if unset
53
-func setRoot(s *Spec) {
54
-	if s.Root == nil {
55
-		s.Root = &specs.Root{}
56
-	}
57
-}
58
-
59
-// setLinux sets Linux to empty if unset
60
-func setLinux(s *Spec) {
61
-	if s.Linux == nil {
62
-		s.Linux = &specs.Linux{}
63
-	}
64
-}
65
-
66
-// setCapabilities sets Linux Capabilities to empty if unset
67
-func setCapabilities(s *Spec) {
68
-	setProcess(s)
69
-	if s.Process.Capabilities == nil {
70
-		s.Process.Capabilities = &specs.LinuxCapabilities{}
71
-	}
72
-}
73
-
74
-// WithHostNamespace allows a task to run inside the host's linux namespace
75
-func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
76
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
77
-		setLinux(s)
78
-		for i, n := range s.Linux.Namespaces {
79
-			if n.Type == ns {
80
-				s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
81
-				return nil
82
-			}
83
-		}
84
-		return nil
85
-	}
86
-}
87
-
88
-// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
89
-// spec, the existing namespace is replaced by the one provided.
90
-func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
91
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
92
-		setLinux(s)
93
-		for i, n := range s.Linux.Namespaces {
94
-			if n.Type == ns.Type {
95
-				before := s.Linux.Namespaces[:i]
96
-				after := s.Linux.Namespaces[i+1:]
97
-				s.Linux.Namespaces = append(before, ns)
98
-				s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
99
-				return nil
100
-			}
101
-		}
102
-		s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
103
-		return nil
104
-	}
105
-}
106
-
107
-// WithImageConfig configures the spec to from the configuration of an Image
108
-func WithImageConfig(image Image) SpecOpts {
109
-	return WithImageConfigArgs(image, nil)
110
-}
111
-
112
-// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
113
-// replaces the CMD of the image
114
-func WithImageConfigArgs(image Image, args []string) SpecOpts {
115
-	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
116
-		ic, err := image.Config(ctx)
117
-		if err != nil {
118
-			return err
119
-		}
120
-		var (
121
-			ociimage v1.Image
122
-			config   v1.ImageConfig
123
-		)
124
-		switch ic.MediaType {
125
-		case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
126
-			p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
127
-			if err != nil {
128
-				return err
129
-			}
130
-
131
-			if err := json.Unmarshal(p, &ociimage); err != nil {
132
-				return err
133
-			}
134
-			config = ociimage.Config
135
-		default:
136
-			return fmt.Errorf("unknown image config media type %s", ic.MediaType)
137
-		}
138
-
139
-		setProcess(s)
140
-		s.Process.Env = append(s.Process.Env, config.Env...)
141
-		cmd := config.Cmd
142
-		if len(args) > 0 {
143
-			cmd = args
144
-		}
145
-		s.Process.Args = append(config.Entrypoint, cmd...)
146
-
147
-		cwd := config.WorkingDir
148
-		if cwd == "" {
149
-			cwd = "/"
150
-		}
151
-		s.Process.Cwd = cwd
152
-		if config.User != "" {
153
-			return WithUser(config.User)(ctx, client, c, s)
154
-		}
155
-		return nil
156
-	}
157
-}
158
-
159
-// WithRootFSPath specifies unmanaged rootfs path.
160
-func WithRootFSPath(path string) SpecOpts {
161
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
162
-		setRoot(s)
163
-		s.Root.Path = path
164
-		// Entrypoint is not set here (it's up to caller)
165
-		return nil
166
-	}
167
-}
168
-
169
-// WithRootFSReadonly sets specs.Root.Readonly to true
170
-func WithRootFSReadonly() SpecOpts {
171
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
172
-		setRoot(s)
173
-		s.Root.Readonly = true
174
-		return nil
175
-	}
176
-}
177
-
178
-// WithNoNewPrivileges sets no_new_privileges on the process for the container
179
-func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
180
-	setProcess(s)
181
-	s.Process.NoNewPrivileges = true
182
-	return nil
183
-}
184
-
185
-// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
186
-func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
187
-	s.Mounts = append(s.Mounts, specs.Mount{
188
-		Destination: "/etc/hosts",
189
-		Type:        "bind",
190
-		Source:      "/etc/hosts",
191
-		Options:     []string{"rbind", "ro"},
192
-	})
193
-	return nil
194
-}
195
-
196
-// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
197
-func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
198
-	s.Mounts = append(s.Mounts, specs.Mount{
199
-		Destination: "/etc/resolv.conf",
200
-		Type:        "bind",
201
-		Source:      "/etc/resolv.conf",
202
-		Options:     []string{"rbind", "ro"},
203
-	})
204
-	return nil
205
-}
206
-
207
-// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
208
-func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
209
-	s.Mounts = append(s.Mounts, specs.Mount{
210
-		Destination: "/etc/localtime",
211
-		Type:        "bind",
212
-		Source:      "/etc/localtime",
213
-		Options:     []string{"rbind", "ro"},
214
-	})
215
-	return nil
216
-}
217
-
218
-// WithUserNamespace sets the uid and gid mappings for the task
219
-// this can be called multiple times to add more mappings to the generated spec
220
-func WithUserNamespace(container, host, size uint32) SpecOpts {
221
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
222
-		var hasUserns bool
223
-		setLinux(s)
224
-		for _, ns := range s.Linux.Namespaces {
225
-			if ns.Type == specs.UserNamespace {
226
-				hasUserns = true
227
-				break
228
-			}
229
-		}
230
-		if !hasUserns {
231
-			s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
232
-				Type: specs.UserNamespace,
233
-			})
234
-		}
235
-		mapping := specs.LinuxIDMapping{
236
-			ContainerID: container,
237
-			HostID:      host,
238
-			Size:        size,
239
-		}
240
-		s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
241
-		s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
242
-		return nil
243
-	}
244
-}
245
-
246
-// WithCgroup sets the container's cgroup path
247
-func WithCgroup(path string) SpecOpts {
248
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
249
-		setLinux(s)
250
-		s.Linux.CgroupsPath = path
251
-		return nil
252
-	}
253
-}
254
-
255
-// WithNamespacedCgroup uses the namespace set on the context to create a
256
-// root directory for containers in the cgroup with the id as the subcgroup
257
-func WithNamespacedCgroup() SpecOpts {
258
-	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
259
-		namespace, err := namespaces.NamespaceRequired(ctx)
260
-		if err != nil {
261
-			return err
262
-		}
263
-		setLinux(s)
264
-		s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
265
-		return nil
266
-	}
267
-}
268
-
269
-// WithUser sets the user to be used within the container.
270
-// It accepts a valid user string in OCI Image Spec v1.0.0:
271
-//   user, uid, user:group, uid:gid, uid:group, user:gid
272
-func WithUser(userstr string) SpecOpts {
273
-	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
274
-		setProcess(s)
275
-		parts := strings.Split(userstr, ":")
276
-		switch len(parts) {
277
-		case 1:
278
-			v, err := strconv.Atoi(parts[0])
279
-			if err != nil {
280
-				// if we cannot parse as a uint they try to see if it is a username
281
-				return WithUsername(userstr)(ctx, client, c, s)
282
-			}
283
-			return WithUserID(uint32(v))(ctx, client, c, s)
284
-		case 2:
285
-			var (
286
-				username  string
287
-				groupname string
288
-			)
289
-			var uid, gid uint32
290
-			v, err := strconv.Atoi(parts[0])
291
-			if err != nil {
292
-				username = parts[0]
293
-			} else {
294
-				uid = uint32(v)
295
-			}
296
-			if v, err = strconv.Atoi(parts[1]); err != nil {
297
-				groupname = parts[1]
298
-			} else {
299
-				gid = uint32(v)
300
-			}
301
-			if username == "" && groupname == "" {
302
-				s.Process.User.UID, s.Process.User.GID = uid, gid
303
-				return nil
304
-			}
305
-			f := func(root string) error {
306
-				if username != "" {
307
-					uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
308
-						return u.Name == username
309
-					})
310
-					if err != nil {
311
-						return err
312
-					}
313
-				}
314
-				if groupname != "" {
315
-					gid, err = getGIDFromPath(root, func(g user.Group) bool {
316
-						return g.Name == groupname
317
-					})
318
-					if err != nil {
319
-						return err
320
-					}
321
-				}
322
-				s.Process.User.UID, s.Process.User.GID = uid, gid
323
-				return nil
324
-			}
325
-			if c.Snapshotter == "" && c.SnapshotKey == "" {
326
-				if !isRootfsAbs(s.Root.Path) {
327
-					return errors.New("rootfs absolute path is required")
328
-				}
329
-				return f(s.Root.Path)
330
-			}
331
-			if c.Snapshotter == "" {
332
-				return errors.New("no snapshotter set for container")
333
-			}
334
-			if c.SnapshotKey == "" {
335
-				return errors.New("rootfs snapshot not created for container")
336
-			}
337
-			snapshotter := client.SnapshotService(c.Snapshotter)
338
-			mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
339
-			if err != nil {
340
-				return err
341
-			}
342
-			return mount.WithTempMount(ctx, mounts, f)
343
-		default:
344
-			return fmt.Errorf("invalid USER value %s", userstr)
345
-		}
346
-	}
347
-}
348
-
349
-// WithUIDGID allows the UID and GID for the Process to be set
350
-func WithUIDGID(uid, gid uint32) SpecOpts {
351
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
352
-		setProcess(s)
353
-		s.Process.User.UID = uid
354
-		s.Process.User.GID = gid
355
-		return nil
356
-	}
357
-}
358
-
359
-// WithUserID sets the correct UID and GID for the container based
360
-// on the image's /etc/passwd contents. If /etc/passwd does not exist,
361
-// or uid is not found in /etc/passwd, it sets the requested uid,
362
-// additionally sets the gid to 0, and does not return an error.
363
-func WithUserID(uid uint32) SpecOpts {
364
-	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
365
-		setProcess(s)
366
-		if c.Snapshotter == "" && c.SnapshotKey == "" {
367
-			if !isRootfsAbs(s.Root.Path) {
368
-				return errors.Errorf("rootfs absolute path is required")
369
-			}
370
-			uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
371
-				return u.Uid == int(uid)
372
-			})
373
-			if err != nil {
374
-				if os.IsNotExist(err) || err == errNoUsersFound {
375
-					s.Process.User.UID, s.Process.User.GID = uid, 0
376
-					return nil
377
-				}
378
-				return err
379
-			}
380
-			s.Process.User.UID, s.Process.User.GID = uuid, ugid
381
-			return nil
382
-
383
-		}
384
-		if c.Snapshotter == "" {
385
-			return errors.Errorf("no snapshotter set for container")
386
-		}
387
-		if c.SnapshotKey == "" {
388
-			return errors.Errorf("rootfs snapshot not created for container")
389
-		}
390
-		snapshotter := client.SnapshotService(c.Snapshotter)
391
-		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
392
-		if err != nil {
393
-			return err
394
-		}
395
-		return mount.WithTempMount(ctx, mounts, func(root string) error {
396
-			uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
397
-				return u.Uid == int(uid)
398
-			})
399
-			if err != nil {
400
-				if os.IsNotExist(err) || err == errNoUsersFound {
401
-					s.Process.User.UID, s.Process.User.GID = uid, 0
402
-					return nil
403
-				}
404
-				return err
405
-			}
406
-			s.Process.User.UID, s.Process.User.GID = uuid, ugid
407
-			return nil
408
-		})
409
-	}
410
-}
411
-
412
-// WithUsername sets the correct UID and GID for the container
413
-// based on the the image's /etc/passwd contents. If /etc/passwd
414
-// does not exist, or the username is not found in /etc/passwd,
415
-// it returns error.
416
-func WithUsername(username string) SpecOpts {
417
-	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
418
-		setProcess(s)
419
-		if c.Snapshotter == "" && c.SnapshotKey == "" {
420
-			if !isRootfsAbs(s.Root.Path) {
421
-				return errors.Errorf("rootfs absolute path is required")
422
-			}
423
-			uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
424
-				return u.Name == username
425
-			})
426
-			if err != nil {
427
-				return err
428
-			}
429
-			s.Process.User.UID, s.Process.User.GID = uid, gid
430
-			return nil
431
-		}
432
-		if c.Snapshotter == "" {
433
-			return errors.Errorf("no snapshotter set for container")
434
-		}
435
-		if c.SnapshotKey == "" {
436
-			return errors.Errorf("rootfs snapshot not created for container")
437
-		}
438
-		snapshotter := client.SnapshotService(c.Snapshotter)
439
-		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
440
-		if err != nil {
441
-			return err
442
-		}
443
-		return mount.WithTempMount(ctx, mounts, func(root string) error {
444
-			uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
445
-				return u.Name == username
446
-			})
447
-			if err != nil {
448
-				return err
449
-			}
450
-			s.Process.User.UID, s.Process.User.GID = uid, gid
451
-			return nil
452
-		})
453
-	}
454
-}
455
-
456
-// WithCapabilities sets Linux capabilities on the process
457
-func WithCapabilities(caps []string) SpecOpts {
458
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
459
-		setCapabilities(s)
460
-
461
-		s.Process.Capabilities.Bounding = caps
462
-		s.Process.Capabilities.Effective = caps
463
-		s.Process.Capabilities.Permitted = caps
464
-		s.Process.Capabilities.Inheritable = caps
465
-
466
-		return nil
467
-	}
468
-}
469
-
470
-// WithAllCapabilities sets all linux capabilities for the process
471
-var WithAllCapabilities = WithCapabilities(getAllCapabilities())
472
-
473
-func getAllCapabilities() []string {
474
-	last := capability.CAP_LAST_CAP
475
-	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
476
-	if last == capability.Cap(63) {
477
-		last = capability.CAP_BLOCK_SUSPEND
478
-	}
479
-	var caps []string
480
-	for _, cap := range capability.List() {
481
-		if cap > last {
482
-			continue
483
-		}
484
-		caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
485
-	}
486
-	return caps
487
-}
488
-
489
-// WithAmbientCapabilities set the Linux ambient capabilities for the process
490
-// Ambient capabilities should only be set for non-root users or the caller should
491
-// understand how these capabilities are used and set
492
-func WithAmbientCapabilities(caps []string) SpecOpts {
493
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
494
-		setCapabilities(s)
495
-
496
-		s.Process.Capabilities.Ambient = caps
497
-		return nil
498
-	}
499
-}
500
-
501
-var errNoUsersFound = errors.New("no users found")
502
-
503
-func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
504
-	ppath, err := fs.RootPath(root, "/etc/passwd")
505
-	if err != nil {
506
-		return 0, 0, err
507
-	}
508
-	users, err := user.ParsePasswdFileFilter(ppath, filter)
509
-	if err != nil {
510
-		return 0, 0, err
511
-	}
512
-	if len(users) == 0 {
513
-		return 0, 0, errNoUsersFound
514
-	}
515
-	u := users[0]
516
-	return uint32(u.Uid), uint32(u.Gid), nil
517
-}
518
-
519
-var errNoGroupsFound = errors.New("no groups found")
520
-
521
-func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
522
-	gpath, err := fs.RootPath(root, "/etc/group")
523
-	if err != nil {
524
-		return 0, err
525
-	}
526
-	groups, err := user.ParseGroupFileFilter(gpath, filter)
527
-	if err != nil {
528
-		return 0, err
529
-	}
530
-	if len(groups) == 0 {
531
-		return 0, errNoGroupsFound
532
-	}
533
-	g := groups[0]
534
-	return uint32(g.Gid), nil
535
-}
536
-
537
-func isRootfsAbs(root string) bool {
538
-	return filepath.IsAbs(root)
539
-}
540
-
541
-// WithMaskedPaths sets the masked paths option
542
-func WithMaskedPaths(paths []string) SpecOpts {
543
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
544
-		setLinux(s)
545
-		s.Linux.MaskedPaths = paths
546
-		return nil
547
-	}
548
-}
549
-
550
-// WithReadonlyPaths sets the read only paths option
551
-func WithReadonlyPaths(paths []string) SpecOpts {
552
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
553
-		setLinux(s)
554
-		s.Linux.ReadonlyPaths = paths
555
-		return nil
556
-	}
557
-}
558
-
559
-// WithWriteableSysfs makes any sysfs mounts writeable
560
-func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
561
-	for i, m := range s.Mounts {
562
-		if m.Type == "sysfs" {
563
-			var options []string
564
-			for _, o := range m.Options {
565
-				if o == "ro" {
566
-					o = "rw"
567
-				}
568
-				options = append(options, o)
569
-			}
570
-			s.Mounts[i].Options = options
571
-		}
572
-	}
573
-	return nil
574
-}
575
-
576
-// WithWriteableCgroupfs makes any cgroup mounts writeable
577
-func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
578
-	for i, m := range s.Mounts {
579
-		if m.Type == "cgroup" {
580
-			var options []string
581
-			for _, o := range m.Options {
582
-				if o == "ro" {
583
-					o = "rw"
584
-				}
585
-				options = append(options, o)
586
-			}
587
-			s.Mounts[i].Options = options
588
-		}
589
-	}
590
-	return nil
591
-}
592
-
593
-// WithSelinuxLabel sets the process SELinux label
594
-func WithSelinuxLabel(label string) SpecOpts {
595
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
596
-		setProcess(s)
597
-		s.Process.SelinuxLabel = label
598
-		return nil
599
-	}
600
-}
601
-
602
-// WithApparmorProfile sets the Apparmor profile for the process
603
-func WithApparmorProfile(profile string) SpecOpts {
604
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
605
-		setProcess(s)
606
-		s.Process.ApparmorProfile = profile
607
-		return nil
608
-	}
609
-}
610
-
611
-// WithSeccompUnconfined clears the seccomp profile
612
-func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
613
-	setLinux(s)
614
-	s.Linux.Seccomp = nil
615
-	return nil
616
-}
617
-
618
-// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
619
-// allowed and denied devices
620
-func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
621
-	setLinux(s)
622
-	if s.Linux.Resources == nil {
623
-		s.Linux.Resources = &specs.LinuxResources{}
624
-	}
625
-	s.Linux.Resources.Devices = nil
626
-	return nil
627
-}
628
-
629
-// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
630
-// the container's resource cgroup spec
631
-func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
632
-	setLinux(s)
633
-	if s.Linux.Resources == nil {
634
-		s.Linux.Resources = &specs.LinuxResources{}
635
-	}
636
-	intptr := func(i int64) *int64 {
637
-		return &i
638
-	}
639
-	s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
640
-		{
641
-			// "/dev/null",
642
-			Type:   "c",
643
-			Major:  intptr(1),
644
-			Minor:  intptr(3),
645
-			Access: rwm,
646
-			Allow:  true,
647
-		},
648
-		{
649
-			// "/dev/random",
650
-			Type:   "c",
651
-			Major:  intptr(1),
652
-			Minor:  intptr(8),
653
-			Access: rwm,
654
-			Allow:  true,
655
-		},
656
-		{
657
-			// "/dev/full",
658
-			Type:   "c",
659
-			Major:  intptr(1),
660
-			Minor:  intptr(7),
661
-			Access: rwm,
662
-			Allow:  true,
663
-		},
664
-		{
665
-			// "/dev/tty",
666
-			Type:   "c",
667
-			Major:  intptr(5),
668
-			Minor:  intptr(0),
669
-			Access: rwm,
670
-			Allow:  true,
671
-		},
672
-		{
673
-			// "/dev/zero",
674
-			Type:   "c",
675
-			Major:  intptr(1),
676
-			Minor:  intptr(5),
677
-			Access: rwm,
678
-			Allow:  true,
679
-		},
680
-		{
681
-			// "/dev/urandom",
682
-			Type:   "c",
683
-			Major:  intptr(1),
684
-			Minor:  intptr(9),
685
-			Access: rwm,
686
-			Allow:  true,
687
-		},
688
-		{
689
-			// "/dev/console",
690
-			Type:   "c",
691
-			Major:  intptr(5),
692
-			Minor:  intptr(1),
693
-			Access: rwm,
694
-			Allow:  true,
695
-		},
696
-		// /dev/pts/ - pts namespaces are "coming soon"
697
-		{
698
-			Type:   "c",
699
-			Major:  intptr(136),
700
-			Access: rwm,
701
-			Allow:  true,
702
-		},
703
-		{
704
-			Type:   "c",
705
-			Major:  intptr(5),
706
-			Minor:  intptr(2),
707
-			Access: rwm,
708
-			Allow:  true,
709
-		},
710
-		{
711
-			// tuntap
712
-			Type:   "c",
713
-			Major:  intptr(10),
714
-			Minor:  intptr(200),
715
-			Access: rwm,
716
-			Allow:  true,
717
-		},
718
-	}...)
719
-	return nil
720
-}
721
-
722
-// WithPrivileged sets up options for a privileged container
723
-// TODO(justincormack) device handling
724
-var WithPrivileged = Compose(
725
-	WithAllCapabilities,
726
-	WithMaskedPaths(nil),
727
-	WithReadonlyPaths(nil),
728
-	WithWriteableSysfs,
729
-	WithWriteableCgroupfs,
730
-	WithSelinuxLabel(""),
731
-	WithApparmorProfile(""),
732
-	WithSeccompUnconfined,
733
-)
734 1
deleted file mode 100644
... ...
@@ -1,89 +0,0 @@
1
-// +build windows
2
-
3
-/*
4
-   Copyright The containerd Authors.
5
-
6
-   Licensed under the Apache License, Version 2.0 (the "License");
7
-   you may not use this file except in compliance with the License.
8
-   You may obtain a copy of the License at
9
-
10
-       http://www.apache.org/licenses/LICENSE-2.0
11
-
12
-   Unless required by applicable law or agreed to in writing, software
13
-   distributed under the License is distributed on an "AS IS" BASIS,
14
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
-   See the License for the specific language governing permissions and
16
-   limitations under the License.
17
-*/
18
-
19
-package oci
20
-
21
-import (
22
-	"context"
23
-	"encoding/json"
24
-	"fmt"
25
-
26
-	"github.com/containerd/containerd/containers"
27
-	"github.com/containerd/containerd/content"
28
-	"github.com/containerd/containerd/images"
29
-	"github.com/opencontainers/image-spec/specs-go/v1"
30
-	specs "github.com/opencontainers/runtime-spec/specs-go"
31
-)
32
-
33
-// WithImageConfig configures the spec to from the configuration of an Image
34
-func WithImageConfig(image Image) SpecOpts {
35
-	return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error {
36
-		setProcess(s)
37
-		ic, err := image.Config(ctx)
38
-		if err != nil {
39
-			return err
40
-		}
41
-		var (
42
-			ociimage v1.Image
43
-			config   v1.ImageConfig
44
-		)
45
-		switch ic.MediaType {
46
-		case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
47
-			p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
48
-			if err != nil {
49
-				return err
50
-			}
51
-			if err := json.Unmarshal(p, &ociimage); err != nil {
52
-				return err
53
-			}
54
-			config = ociimage.Config
55
-		default:
56
-			return fmt.Errorf("unknown image config media type %s", ic.MediaType)
57
-		}
58
-		s.Process.Env = config.Env
59
-		s.Process.Args = append(config.Entrypoint, config.Cmd...)
60
-		s.Process.User = specs.User{
61
-			Username: config.User,
62
-		}
63
-		return nil
64
-	}
65
-}
66
-
67
-// WithTTY sets the information on the spec as well as the environment variables for
68
-// using a TTY
69
-func WithTTY(width, height int) SpecOpts {
70
-	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
71
-		setProcess(s)
72
-		s.Process.Terminal = true
73
-		if s.Process.ConsoleSize == nil {
74
-			s.Process.ConsoleSize = &specs.Box{}
75
-		}
76
-		s.Process.ConsoleSize.Width = uint(width)
77
-		s.Process.ConsoleSize.Height = uint(height)
78
-		return nil
79
-	}
80
-}
81
-
82
-// WithUsername sets the username on the process
83
-func WithUsername(username string) SpecOpts {
84
-	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
85
-		setProcess(s)
86
-		s.Process.User.Username = username
87
-		return nil
88
-	}
89
-}
90 1
deleted file mode 100644
... ...
@@ -1,188 +0,0 @@
1
-// +build !windows
2
-
3
-/*
4
-   Copyright The containerd Authors.
5
-
6
-   Licensed under the Apache License, Version 2.0 (the "License");
7
-   you may not use this file except in compliance with the License.
8
-   You may obtain a copy of the License at
9
-
10
-       http://www.apache.org/licenses/LICENSE-2.0
11
-
12
-   Unless required by applicable law or agreed to in writing, software
13
-   distributed under the License is distributed on an "AS IS" BASIS,
14
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
-   See the License for the specific language governing permissions and
16
-   limitations under the License.
17
-*/
18
-
19
-package oci
20
-
21
-import (
22
-	"context"
23
-	"path/filepath"
24
-
25
-	"github.com/containerd/containerd/namespaces"
26
-	specs "github.com/opencontainers/runtime-spec/specs-go"
27
-)
28
-
29
-const (
30
-	rwm               = "rwm"
31
-	defaultRootfsPath = "rootfs"
32
-)
33
-
34
-var (
35
-	defaultEnv = []string{
36
-		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
37
-	}
38
-)
39
-
40
-func defaultCaps() []string {
41
-	return []string{
42
-		"CAP_CHOWN",
43
-		"CAP_DAC_OVERRIDE",
44
-		"CAP_FSETID",
45
-		"CAP_FOWNER",
46
-		"CAP_MKNOD",
47
-		"CAP_NET_RAW",
48
-		"CAP_SETGID",
49
-		"CAP_SETUID",
50
-		"CAP_SETFCAP",
51
-		"CAP_SETPCAP",
52
-		"CAP_NET_BIND_SERVICE",
53
-		"CAP_SYS_CHROOT",
54
-		"CAP_KILL",
55
-		"CAP_AUDIT_WRITE",
56
-	}
57
-}
58
-
59
-func defaultNamespaces() []specs.LinuxNamespace {
60
-	return []specs.LinuxNamespace{
61
-		{
62
-			Type: specs.PIDNamespace,
63
-		},
64
-		{
65
-			Type: specs.IPCNamespace,
66
-		},
67
-		{
68
-			Type: specs.UTSNamespace,
69
-		},
70
-		{
71
-			Type: specs.MountNamespace,
72
-		},
73
-		{
74
-			Type: specs.NetworkNamespace,
75
-		},
76
-	}
77
-}
78
-
79
-func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
80
-	ns, err := namespaces.NamespaceRequired(ctx)
81
-	if err != nil {
82
-		return err
83
-	}
84
-
85
-	*s = Spec{
86
-		Version: specs.Version,
87
-		Root: &specs.Root{
88
-			Path: defaultRootfsPath,
89
-		},
90
-		Process: &specs.Process{
91
-			Env:             defaultEnv,
92
-			Cwd:             "/",
93
-			NoNewPrivileges: true,
94
-			User: specs.User{
95
-				UID: 0,
96
-				GID: 0,
97
-			},
98
-			Capabilities: &specs.LinuxCapabilities{
99
-				Bounding:    defaultCaps(),
100
-				Permitted:   defaultCaps(),
101
-				Inheritable: defaultCaps(),
102
-				Effective:   defaultCaps(),
103
-			},
104
-			Rlimits: []specs.POSIXRlimit{
105
-				{
106
-					Type: "RLIMIT_NOFILE",
107
-					Hard: uint64(1024),
108
-					Soft: uint64(1024),
109
-				},
110
-			},
111
-		},
112
-		Mounts: []specs.Mount{
113
-			{
114
-				Destination: "/proc",
115
-				Type:        "proc",
116
-				Source:      "proc",
117
-			},
118
-			{
119
-				Destination: "/dev",
120
-				Type:        "tmpfs",
121
-				Source:      "tmpfs",
122
-				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
123
-			},
124
-			{
125
-				Destination: "/dev/pts",
126
-				Type:        "devpts",
127
-				Source:      "devpts",
128
-				Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
129
-			},
130
-			{
131
-				Destination: "/dev/shm",
132
-				Type:        "tmpfs",
133
-				Source:      "shm",
134
-				Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
135
-			},
136
-			{
137
-				Destination: "/dev/mqueue",
138
-				Type:        "mqueue",
139
-				Source:      "mqueue",
140
-				Options:     []string{"nosuid", "noexec", "nodev"},
141
-			},
142
-			{
143
-				Destination: "/sys",
144
-				Type:        "sysfs",
145
-				Source:      "sysfs",
146
-				Options:     []string{"nosuid", "noexec", "nodev", "ro"},
147
-			},
148
-			{
149
-				Destination: "/run",
150
-				Type:        "tmpfs",
151
-				Source:      "tmpfs",
152
-				Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
153
-			},
154
-		},
155
-		Linux: &specs.Linux{
156
-			MaskedPaths: []string{
157
-				"/proc/acpi",
158
-				"/proc/kcore",
159
-				"/proc/keys",
160
-				"/proc/latency_stats",
161
-				"/proc/timer_list",
162
-				"/proc/timer_stats",
163
-				"/proc/sched_debug",
164
-				"/sys/firmware",
165
-				"/proc/scsi",
166
-			},
167
-			ReadonlyPaths: []string{
168
-				"/proc/asound",
169
-				"/proc/bus",
170
-				"/proc/fs",
171
-				"/proc/irq",
172
-				"/proc/sys",
173
-				"/proc/sysrq-trigger",
174
-			},
175
-			CgroupsPath: filepath.Join("/", ns, id),
176
-			Resources: &specs.LinuxResources{
177
-				Devices: []specs.LinuxDeviceCgroup{
178
-					{
179
-						Allow:  false,
180
-						Access: rwm,
181
-					},
182
-				},
183
-			},
184
-			Namespaces: defaultNamespaces(),
185
-		},
186
-	}
187
-	return nil
188
-}
189 1
deleted file mode 100644
... ...
@@ -1,44 +0,0 @@
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 oci
18
-
19
-import (
20
-	"context"
21
-
22
-	specs "github.com/opencontainers/runtime-spec/specs-go"
23
-)
24
-
25
-func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
26
-	*s = Spec{
27
-		Version: specs.Version,
28
-		Root:    &specs.Root{},
29
-		Process: &specs.Process{
30
-			Cwd: `C:\`,
31
-			ConsoleSize: &specs.Box{
32
-				Width:  80,
33
-				Height: 20,
34
-			},
35
-		},
36
-		Windows: &specs.Windows{
37
-			IgnoreFlushesDuringBoot: true,
38
-			Network: &specs.WindowsNetwork{
39
-				AllowUnqualifiedDNSQuery: true,
40
-			},
41
-		},
42
-	}
43
-	return nil
44
-}
... ...
@@ -22,11 +22,6 @@ import (
22 22
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
23 23
 )
24 24
 
25
-// Default returns the default matcher for the platform.
26
-func Default() MatchComparer {
27
-	return Only(DefaultSpec())
28
-}
29
-
30 25
 // DefaultString returns the default string specifier for the platform.
31 26
 func DefaultString() string {
32 27
 	return Format(DefaultSpec())
33 28
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+// +build !windows
1
+
2
+/*
3
+   Copyright The containerd Authors.
4
+
5
+   Licensed under the Apache License, Version 2.0 (the "License");
6
+   you may not use this file except in compliance with the License.
7
+   You may obtain a copy of the License at
8
+
9
+       http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+   Unless required by applicable law or agreed to in writing, software
12
+   distributed under the License is distributed on an "AS IS" BASIS,
13
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+   See the License for the specific language governing permissions and
15
+   limitations under the License.
16
+*/
17
+
18
+package platforms
19
+
20
+// Default returns the default matcher for the platform.
21
+func Default() MatchComparer {
22
+	return Only(DefaultSpec())
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+// +build windows
1
+
2
+/*
3
+   Copyright The containerd Authors.
4
+
5
+   Licensed under the Apache License, Version 2.0 (the "License");
6
+   you may not use this file except in compliance with the License.
7
+   You may obtain a copy of the License at
8
+
9
+       http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+   Unless required by applicable law or agreed to in writing, software
12
+   distributed under the License is distributed on an "AS IS" BASIS,
13
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+   See the License for the specific language governing permissions and
15
+   limitations under the License.
16
+*/
17
+
18
+package platforms
19
+
20
+import (
21
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
22
+)
23
+
24
+// Default returns the default matcher for the platform.
25
+func Default() MatchComparer {
26
+	return Ordered(DefaultSpec(), specs.Platform{
27
+		OS:           "linux",
28
+		Architecture: "amd64",
29
+	})
30
+}
... ...
@@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
117 117
 			}
118 118
 		} else {
119 119
 			// TODO: Should any cases where use of content range
120
-			// without the proper header be considerd?
120
+			// without the proper header be considered?
121 121
 			// 206 responses?
122 122
 
123 123
 			// Discard up to offset
... ...
@@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
134 134
 		// There is an edge case here where offset == size of the content. If
135 135
 		// we seek, we will probably get an error for content that cannot be
136 136
 		// sought (?). In that case, we should err on committing the content,
137
-		// as the length is already satisified but we just return the empty
137
+		// as the length is already satisfied but we just return the empty
138 138
 		// reader instead.
139 139
 
140 140
 		hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
... ...
@@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
272 272
 			return err
273 273
 		}
274 274
 
275
-		// TODO: Check if blob -> diff id mapping already exists
276
-		// TODO: Check if blob empty label exists
275
+		reuse, err := c.reuseLabelBlobState(ctx, desc)
276
+		if err != nil {
277
+			return err
278
+		}
279
+
280
+		if reuse {
281
+			return nil
282
+		}
277 283
 
278 284
 		ra, err := c.contentStore.ReaderAt(ctx, desc)
279 285
 		if err != nil {
... ...
@@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
343 343
 
344 344
 	state := calc.State()
345 345
 
346
+	cinfo := content.Info{
347
+		Digest: desc.Digest,
348
+		Labels: map[string]string{
349
+			"containerd.io/uncompressed": state.diffID.String(),
350
+		},
351
+	}
352
+
353
+	if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
354
+		return errors.Wrap(err, "failed to update uncompressed label")
355
+	}
356
+
346 357
 	c.mu.Lock()
347 358
 	c.blobMap[desc.Digest] = state
348 359
 	c.layerBlobs[state.diffID] = desc
... ...
@@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
351 351
 	return nil
352 352
 }
353 353
 
354
+func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) {
355
+	cinfo, err := c.contentStore.Info(ctx, desc.Digest)
356
+	if err != nil {
357
+		return false, errors.Wrap(err, "failed to get blob info")
358
+	}
359
+	desc.Size = cinfo.Size
360
+
361
+	diffID, ok := cinfo.Labels["containerd.io/uncompressed"]
362
+	if !ok {
363
+		return false, nil
364
+	}
365
+
366
+	bState := blobState{empty: false}
367
+
368
+	if bState.diffID, err = digest.Parse(diffID); err != nil {
369
+		log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
370
+		return false, nil
371
+	}
372
+
373
+	// NOTE: there is no need to read header to get compression method
374
+	// because there are only two kinds of methods.
375
+	if bState.diffID == desc.Digest {
376
+		desc.MediaType = images.MediaTypeDockerSchema2Layer
377
+	} else {
378
+		desc.MediaType = images.MediaTypeDockerSchema2LayerGzip
379
+	}
380
+
381
+	c.mu.Lock()
382
+	c.blobMap[desc.Digest] = bState
383
+	c.layerBlobs[bState.diffID] = desc
384
+	c.mu.Unlock()
385
+	return true, nil
386
+}
387
+
354 388
 func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
355 389
 	if c.pulledManifest == nil {
356 390
 		return nil, nil, errors.New("missing schema 1 manifest for conversion")
... ...
@@ -147,7 +147,7 @@ func (e *execProcess) start(ctx context.Context) (err error) {
147 147
 			return errors.Wrap(err, "creating new NULL IO")
148 148
 		}
149 149
 	} else {
150
-		if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID); err != nil {
150
+		if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)); err != nil {
151 151
 			return errors.Wrap(err, "failed to create runc io pipes")
152 152
 		}
153 153
 	}
... ...
@@ -60,11 +60,11 @@ func (s *execCreatedState) Start(ctx context.Context) error {
60 60
 }
61 61
 
62 62
 func (s *execCreatedState) Delete(ctx context.Context) error {
63
-	s.p.mu.Lock()
64
-	defer s.p.mu.Unlock()
65 63
 	if err := s.p.delete(ctx); err != nil {
66 64
 		return err
67 65
 	}
66
+	s.p.mu.Lock()
67
+	defer s.p.mu.Unlock()
68 68
 	return s.transition("deleted")
69 69
 }
70 70
 
... ...
@@ -168,11 +168,11 @@ func (s *execStoppedState) Start(ctx context.Context) error {
168 168
 }
169 169
 
170 170
 func (s *execStoppedState) Delete(ctx context.Context) error {
171
-	s.p.mu.Lock()
172
-	defer s.p.mu.Unlock()
173 171
 	if err := s.p.delete(ctx); err != nil {
174 172
 		return err
175 173
 	}
174
+	s.p.mu.Lock()
175
+	defer s.p.mu.Unlock()
176 176
 	return s.transition("deleted")
177 177
 }
178 178
 
... ...
@@ -123,7 +123,7 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
123 123
 			return errors.Wrap(err, "creating new NULL IO")
124 124
 		}
125 125
 	} else {
126
-		if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID); err != nil {
126
+		if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID, withConditionalIO(p.stdio)); err != nil {
127 127
 			return errors.Wrap(err, "failed to create OCI runtime io pipes")
128 128
 		}
129 129
 	}
... ...
@@ -228,7 +228,7 @@ func (p *Init) Status(ctx context.Context) (string, error) {
228 228
 	defer p.mu.Unlock()
229 229
 	c, err := p.runtime.State(ctx, p.id)
230 230
 	if err != nil {
231
-		if os.IsNotExist(err) {
231
+		if strings.Contains(err.Error(), "does not exist") {
232 232
 			return "stopped", nil
233 233
 		}
234 234
 		return "", p.runtimeError(err, "OCI runtime state failed")
... ...
@@ -249,7 +249,6 @@ func (p *Init) setExited(status int) {
249 249
 }
250 250
 
251 251
 func (p *Init) delete(context context.Context) error {
252
-	p.KillAll(context)
253 252
 	p.wg.Wait()
254 253
 	err := p.runtime.Delete(context, p.id, nil)
255 254
 	// ignore errors if a runtime has already deleted the process
... ...
@@ -400,3 +399,11 @@ func (p *Init) runtimeError(rErr error, msg string) error {
400 400
 		return errors.Errorf("%s: %s", msg, rMsg)
401 401
 	}
402 402
 }
403
+
404
+func withConditionalIO(c proc.Stdio) runc.IOOpt {
405
+	return func(o *runc.IOOption) {
406
+		o.OpenStdin = c.Stdin != ""
407
+		o.OpenStdout = c.Stdout != ""
408
+		o.OpenStderr = c.Stderr != ""
409
+	}
410
+}
... ...
@@ -109,7 +109,6 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
109 109
 		i.dest(fw, fr)
110 110
 	}
111 111
 	if stdin == "" {
112
-		rio.Stdin().Close()
113 112
 		return nil
114 113
 	}
115 114
 	f, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
... ...
@@ -26,7 +26,6 @@ import (
26 26
 	"path/filepath"
27 27
 	"time"
28 28
 
29
-	"github.com/boltdb/bolt"
30 29
 	eventstypes "github.com/containerd/containerd/api/events"
31 30
 	"github.com/containerd/containerd/api/types"
32 31
 	"github.com/containerd/containerd/containers"
... ...
@@ -49,6 +48,7 @@ import (
49 49
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
50 50
 	"github.com/pkg/errors"
51 51
 	"github.com/sirupsen/logrus"
52
+	bolt "go.etcd.io/bbolt"
52 53
 	"golang.org/x/sys/unix"
53 54
 )
54 55
 
... ...
@@ -204,7 +204,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
204 204
 				log.G(ctx).WithError(err).WithFields(logrus.Fields{
205 205
 					"id":        id,
206 206
 					"namespace": namespace,
207
-				}).Warn("failed to clen up after killed shim")
207
+				}).Warn("failed to clean up after killed shim")
208 208
 			}
209 209
 		}
210 210
 		shimopt = ShimRemote(r.config, r.address, cgroup, exitHandler)
... ...
@@ -248,8 +248,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
248 248
 	if err != nil {
249 249
 		return nil, errdefs.FromGRPC(err)
250 250
 	}
251
-	t, err := newTask(id, namespace, int(cr.Pid), s, r.events,
252
-		proc.NewRunc(ropts.RuntimeRoot, sopts.Bundle, namespace, rt, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle)
251
+	t, err := newTask(id, namespace, int(cr.Pid), s, r.events, r.tasks, bundle)
253 252
 	if err != nil {
254 253
 		return nil, err
255 254
 	}
... ...
@@ -341,15 +340,8 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
341 341
 			}
342 342
 			continue
343 343
 		}
344
-		ropts, err := r.getRuncOptions(ctx, id)
345
-		if err != nil {
346
-			log.G(ctx).WithError(err).WithField("id", id).
347
-				Error("get runtime options")
348
-			continue
349
-		}
350 344
 
351
-		t, err := newTask(id, ns, pid, s, r.events,
352
-			proc.NewRunc(ropts.RuntimeRoot, bundle.path, ns, ropts.Runtime, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle)
345
+		t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle)
353 346
 		if err != nil {
354 347
 			log.G(ctx).WithError(err).Error("loading task type")
355 348
 			continue
... ...
@@ -31,8 +31,7 @@ import (
31 31
 	"github.com/containerd/containerd/log"
32 32
 	"github.com/containerd/containerd/runtime"
33 33
 	"github.com/containerd/containerd/runtime/v1/shim/client"
34
-	shim "github.com/containerd/containerd/runtime/v1/shim/v1"
35
-	runc "github.com/containerd/go-runc"
34
+	"github.com/containerd/containerd/runtime/v1/shim/v1"
36 35
 	"github.com/containerd/ttrpc"
37 36
 	"github.com/containerd/typeurl"
38 37
 	"github.com/gogo/protobuf/types"
... ...
@@ -52,7 +51,7 @@ type Task struct {
52 52
 	bundle    *bundle
53 53
 }
54 54
 
55
-func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, runtime *runc.Runc, list *runtime.TaskList, bundle *bundle) (*Task, error) {
55
+func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, list *runtime.TaskList, bundle *bundle) (*Task, error) {
56 56
 	var (
57 57
 		err error
58 58
 		cg  cgroups.Cgroup
... ...
@@ -20,7 +20,9 @@ package shim
20 20
 
21 21
 import (
22 22
 	"context"
23
+	"encoding/json"
23 24
 	"fmt"
25
+	"io/ioutil"
24 26
 	"os"
25 27
 	"path/filepath"
26 28
 	"sync"
... ...
@@ -41,6 +43,7 @@ import (
41 41
 	runc "github.com/containerd/go-runc"
42 42
 	"github.com/containerd/typeurl"
43 43
 	ptypes "github.com/gogo/protobuf/types"
44
+	specs "github.com/opencontainers/runtime-spec/specs-go"
44 45
 	"github.com/pkg/errors"
45 46
 	"github.com/sirupsen/logrus"
46 47
 	"google.golang.org/grpc/codes"
... ...
@@ -221,19 +224,21 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR
221 221
 
222 222
 // DeleteProcess deletes an exec'd process
223 223
 func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessRequest) (*shimapi.DeleteResponse, error) {
224
-	s.mu.Lock()
225
-	defer s.mu.Unlock()
226 224
 	if r.ID == s.id {
227 225
 		return nil, status.Errorf(codes.InvalidArgument, "cannot delete init process with DeleteProcess")
228 226
 	}
227
+	s.mu.Lock()
229 228
 	p := s.processes[r.ID]
229
+	s.mu.Unlock()
230 230
 	if p == nil {
231 231
 		return nil, errors.Wrapf(errdefs.ErrNotFound, "process %s", r.ID)
232 232
 	}
233 233
 	if err := p.Delete(ctx); err != nil {
234 234
 		return nil, err
235 235
 	}
236
+	s.mu.Lock()
236 237
 	delete(s.processes, r.ID)
238
+	s.mu.Unlock()
237 239
 	return &shimapi.DeleteResponse{
238 240
 		ExitStatus: uint32(p.ExitStatus()),
239 241
 		ExitedAt:   p.ExitedAt(),
... ...
@@ -507,13 +512,22 @@ func (s *Service) processExits() {
507 507
 func (s *Service) checkProcesses(e runc.Exit) {
508 508
 	s.mu.Lock()
509 509
 	defer s.mu.Unlock()
510
+
511
+	shouldKillAll, err := shouldKillAllOnExit(s.bundle)
512
+	if err != nil {
513
+		log.G(s.context).WithError(err).Error("failed to check shouldKillAll")
514
+	}
515
+
510 516
 	for _, p := range s.processes {
511 517
 		if p.Pid() == e.Pid {
512
-			if ip, ok := p.(*proc.Init); ok {
513
-				// Ensure all children are killed
514
-				if err := ip.KillAll(s.context); err != nil {
515
-					log.G(s.context).WithError(err).WithField("id", ip.ID()).
516
-						Error("failed to kill init's children")
518
+
519
+			if shouldKillAll {
520
+				if ip, ok := p.(*proc.Init); ok {
521
+					// Ensure all children are killed
522
+					if err := ip.KillAll(s.context); err != nil {
523
+						log.G(s.context).WithError(err).WithField("id", ip.ID()).
524
+							Error("failed to kill init's children")
525
+					}
517 526
 				}
518 527
 			}
519 528
 			p.SetExited(e.Status)
... ...
@@ -529,6 +543,25 @@ func (s *Service) checkProcesses(e runc.Exit) {
529 529
 	}
530 530
 }
531 531
 
532
+func shouldKillAllOnExit(bundlePath string) (bool, error) {
533
+	var bundleSpec specs.Spec
534
+	bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json"))
535
+	if err != nil {
536
+		return false, err
537
+	}
538
+	json.Unmarshal(bundleConfigContents, &bundleSpec)
539
+
540
+	if bundleSpec.Linux != nil {
541
+		for _, ns := range bundleSpec.Linux.Namespaces {
542
+			if ns.Type == specs.PIDNamespace {
543
+				return false, nil
544
+			}
545
+		}
546
+	}
547
+
548
+	return true, nil
549
+}
550
+
532 551
 func (s *Service) getContainerPids(ctx context.Context, id string) ([]uint32, error) {
533 552
 	s.mu.Lock()
534 553
 	defer s.mu.Unlock()
... ...
@@ -29,7 +29,6 @@ import (
29 29
 	"sync"
30 30
 	"time"
31 31
 
32
-	"github.com/boltdb/bolt"
33 32
 	csapi "github.com/containerd/containerd/api/services/content/v1"
34 33
 	ssapi "github.com/containerd/containerd/api/services/snapshots/v1"
35 34
 	"github.com/containerd/containerd/content"
... ...
@@ -46,6 +45,7 @@ import (
46 46
 	metrics "github.com/docker/go-metrics"
47 47
 	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
48 48
 	"github.com/pkg/errors"
49
+	bolt "go.etcd.io/bbolt"
49 50
 	"google.golang.org/grpc"
50 51
 )
51 52
 
... ...
@@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) {
42 42
 	return net.Listen("unix", path)
43 43
 }
44 44
 
45
-// GetLocalListener returns a listerner out of a unix socket.
45
+// GetLocalListener returns a listener out of a unix socket.
46 46
 func GetLocalListener(path string, uid, gid int) (net.Listener, error) {
47 47
 	// Ensure parent directory is created
48 48
 	if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {
... ...
@@ -607,8 +607,11 @@ func writeContent(ctx context.Context, store content.Ingester, mediaType, ref st
607 607
 	if err != nil {
608 608
 		return d, err
609 609
 	}
610
+
610 611
 	if err := writer.Commit(ctx, size, "", opts...); err != nil {
611
-		return d, err
612
+		if !errdefs.IsAlreadyExists(err) {
613
+			return d, err
614
+		}
612 615
 	}
613 616
 	return v1.Descriptor{
614 617
 		MediaType: mediaType,
... ...
@@ -18,10 +18,18 @@ package containerd
18 18
 
19 19
 import (
20 20
 	"context"
21
+	"encoding/json"
22
+	"fmt"
21 23
 	"syscall"
22 24
 
25
+	"github.com/containerd/containerd/api/types"
26
+	"github.com/containerd/containerd/content"
23 27
 	"github.com/containerd/containerd/errdefs"
28
+	"github.com/containerd/containerd/images"
24 29
 	"github.com/containerd/containerd/mount"
30
+	imagespec "github.com/opencontainers/image-spec/specs-go/v1"
31
+	"github.com/opencontainers/runtime-spec/specs-go"
32
+	"github.com/pkg/errors"
25 33
 )
26 34
 
27 35
 // NewTaskOpts allows the caller to set options on a new task
... ...
@@ -35,6 +43,44 @@ func WithRootFS(mounts []mount.Mount) NewTaskOpts {
35 35
 	}
36 36
 }
37 37
 
38
+// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
39
+// previous checkpoint. Additional software such as CRIU may be required to
40
+// restore a task from a checkpoint
41
+func WithTaskCheckpoint(im Image) NewTaskOpts {
42
+	return func(ctx context.Context, c *Client, info *TaskInfo) error {
43
+		desc := im.Target()
44
+		id := desc.Digest
45
+		index, err := decodeIndex(ctx, c.ContentStore(), desc)
46
+		if err != nil {
47
+			return err
48
+		}
49
+		for _, m := range index.Manifests {
50
+			if m.MediaType == images.MediaTypeContainerd1Checkpoint {
51
+				info.Checkpoint = &types.Descriptor{
52
+					MediaType: m.MediaType,
53
+					Size_:     m.Size,
54
+					Digest:    m.Digest,
55
+				}
56
+				return nil
57
+			}
58
+		}
59
+		return fmt.Errorf("checkpoint not found in index %s", id)
60
+	}
61
+}
62
+
63
+func decodeIndex(ctx context.Context, store content.Provider, desc imagespec.Descriptor) (*imagespec.Index, error) {
64
+	var index imagespec.Index
65
+	p, err := content.ReadBlob(ctx, store, desc)
66
+	if err != nil {
67
+		return nil, err
68
+	}
69
+	if err := json.Unmarshal(p, &index); err != nil {
70
+		return nil, err
71
+	}
72
+
73
+	return &index, nil
74
+}
75
+
38 76
 // WithCheckpointName sets the image name for the checkpoint
39 77
 func WithCheckpointName(name string) CheckpointTaskOpts {
40 78
 	return func(r *CheckpointTaskInfo) error {
... ...
@@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts {
92 92
 		return nil
93 93
 	}
94 94
 }
95
+
96
+// WithResources sets the provided resources for task updates. Resources must be
97
+// either a *specs.LinuxResources or a *specs.WindowsResources
98
+func WithResources(resources interface{}) UpdateTaskOpts {
99
+	return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
100
+		switch resources.(type) {
101
+		case *specs.LinuxResources:
102
+		case *specs.WindowsResources:
103
+		default:
104
+			return errors.New("WithResources requires a *specs.LinuxResources or *specs.WindowsResources")
105
+		}
106
+
107
+		r.Resources = resources
108
+		return nil
109
+	}
110
+}
95 111
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
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 containerd
18
-
19
-import (
20
-	"context"
21
-	"errors"
22
-
23
-	"github.com/containerd/containerd/runtime/linux/runctypes"
24
-	"github.com/opencontainers/runtime-spec/specs-go"
25
-)
26
-
27
-// WithResources sets the provided resources for task updates
28
-func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
29
-	return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
30
-		r.Resources = resources
31
-		return nil
32
-	}
33
-}
34
-
35
-// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
36
-// There is an upper limit on the number of keyrings in a linux system
37
-func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
38
-	if ti.Options == nil {
39
-		ti.Options = &runctypes.CreateOptions{}
40
-	}
41
-	opts, ok := ti.Options.(*runctypes.CreateOptions)
42
-	if !ok {
43
-		return errors.New("could not cast TaskInfo Options to CreateOptions")
44
-	}
45
-
46
-	opts.NoNewKeyring = true
47
-	return nil
48
-}
49 1
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+// +build !windows
1
+
2
+/*
3
+   Copyright The containerd Authors.
4
+
5
+   Licensed under the Apache License, Version 2.0 (the "License");
6
+   you may not use this file except in compliance with the License.
7
+   You may obtain a copy of the License at
8
+
9
+       http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+   Unless required by applicable law or agreed to in writing, software
12
+   distributed under the License is distributed on an "AS IS" BASIS,
13
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+   See the License for the specific language governing permissions and
15
+   limitations under the License.
16
+*/
17
+
18
+package containerd
19
+
20
+import (
21
+	"context"
22
+
23
+	"github.com/containerd/containerd/runtime/linux/runctypes"
24
+	"github.com/pkg/errors"
25
+)
26
+
27
+// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
28
+// There is an upper limit on the number of keyrings in a linux system
29
+func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
30
+	if ti.Options == nil {
31
+		ti.Options = &runctypes.CreateOptions{}
32
+	}
33
+	opts, ok := ti.Options.(*runctypes.CreateOptions)
34
+	if !ok {
35
+		return errors.New("could not cast TaskInfo Options to CreateOptions")
36
+	}
37
+
38
+	opts.NoNewKeyring = true
39
+	return nil
40
+}
41
+
42
+// WithNoPivotRoot instructs the runtime not to you pivot_root
43
+func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
44
+	if info.Options == nil {
45
+		info.Options = &runctypes.CreateOptions{
46
+			NoPivotRoot: true,
47
+		}
48
+		return nil
49
+	}
50
+	opts, ok := info.Options.(*runctypes.CreateOptions)
51
+	if !ok {
52
+		return errors.New("invalid options type, expected runctypes.CreateOptions")
53
+	}
54
+	opts.NoPivotRoot = true
55
+	return nil
56
+}
0 57
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
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 containerd
18
-
19
-import (
20
-	"context"
21
-
22
-	specs "github.com/opencontainers/runtime-spec/specs-go"
23
-)
24
-
25
-// WithResources sets the provided resources on the spec for task updates
26
-func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
27
-	return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
28
-		r.Resources = resources
29
-		return nil
30
-	}
31
-}
... ...
@@ -1,10 +1,10 @@
1
-github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292
1
+github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
2 2
 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
3 3
 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
4 4
 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
5 5
 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
6 6
 github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
7
-github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
7
+github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
8 8
 github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
9 9
 github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
10 10
 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
... ...
@@ -19,7 +19,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0
19 19
 github.com/gogo/protobuf v1.0.0
20 20
 github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
21 21
 github.com/golang/protobuf v1.1.0
22
-github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce # v1.0.1-43-gd810dbc
22
+github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
23 23
 github.com/opencontainers/runc 20aff4f0488c6d4b8df4d85b4f63f1f704c11abd
24 24
 github.com/sirupsen/logrus v1.0.0
25 25
 github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
... ...
@@ -34,17 +34,17 @@ github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
34 34
 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
35 35
 github.com/Microsoft/go-winio v0.4.10
36 36
 github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55
37
-github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
38 37
 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
39 38
 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
40 39
 github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
41 40
 github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
42 41
 gotest.tools v2.1.0
43 42
 github.com/google/go-cmp v0.1.0
43
+go.etcd.io/bbolt v1.3.1-etcd.8
44 44
 
45 45
 # cri dependencies
46
-github.com/containerd/cri v1.11.1
47
-github.com/containerd/go-cni 5882530828ecf62032409b298a3e8b19e08b6534
46
+github.com/containerd/cri 9f39e3289533fc228c5e5fcac0a6dbdd60c6047b # release/1.2 branch
47
+github.com/containerd/go-cni 6d7b509a054a3cb1c35ed1865d4fde2f0cb547cd
48 48
 github.com/blang/semver v3.1.0
49 49
 github.com/containernetworking/cni v0.6.0
50 50
 github.com/containernetworking/plugins v0.7.0
... ...
@@ -52,32 +52,33 @@ github.com/davecgh/go-spew v1.1.0
52 52
 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
53 53
 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
54 54
 github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
55
-github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46
56
-github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee
55
+github.com/emicklei/go-restful v2.2.1
56
+github.com/ghodss/yaml v1.0.0
57 57
 github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed
58 58
 github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
59 59
 github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
60 60
 github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
61
-github.com/json-iterator/go f2b4162afba35581b6d4a50d3b8f34e33c144682
62
-github.com/modern-go/reflect2 05fbef0ca5da472bbf96c9322b84a53edc03c9fd
61
+github.com/json-iterator/go 1.1.5
62
+github.com/modern-go/reflect2 1.0.1
63 63
 github.com/modern-go/concurrent 1.0.3
64 64
 github.com/opencontainers/runtime-tools v0.6.0
65
-github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206
65
+github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a
66 66
 github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
67
-github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc
67
+github.com/tchap/go-patricia v2.2.6
68 68
 github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
69 69
 github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b
70 70
 github.com/xeipuuv/gojsonschema 1d523034197ff1f222f6429836dd36a2457a1874
71 71
 golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
72
+golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
72 73
 golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
73 74
 gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
74
-gopkg.in/yaml.v2 53feefa2559fb8dfa8d81baad31be332c97d6c77
75
-k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722
76
-k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697
77
-k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5
78
-k8s.io/client-go 03bfb9bdcfe5482795b999f39ca3ed9ad42ce5bb
79
-k8s.io/kubernetes v1.11.0
80
-k8s.io/utils 733eca437aa39379e4bcc25e726439dfca40fcff
75
+gopkg.in/yaml.v2 v2.2.1
76
+k8s.io/api 012f271b5d41baad56190c5f1ae19bff16df0fd8
77
+k8s.io/apimachinery 6429050ef506887d121f3e7306e894f8900d8a63
78
+k8s.io/apiserver e9312c15296b6c2c923ebd5031ff5d1d5fd022d7
79
+k8s.io/client-go 37c3c02ec96533daec0dbda1f39a6b1d68505c79
80
+k8s.io/kubernetes v1.12.0-beta.1
81
+k8s.io/utils 982821ea41da7e7c15f3d3738921eb2e7e241ccd
81 82
 
82 83
 # zfs dependencies
83 84
 github.com/containerd/zfs 9a0b8b8b5982014b729cd34eb7cd7a11062aa6ec
... ...
@@ -85,4 +86,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
85 85
 github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
86 86
 
87 87
 # aufs dependencies
88
-github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988
88
+github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92
89 89
new file mode 100644
... ...
@@ -0,0 +1,657 @@
0
+package continuity
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io"
6
+	"log"
7
+	"os"
8
+	"path/filepath"
9
+	"strings"
10
+
11
+	"github.com/containerd/continuity/devices"
12
+	driverpkg "github.com/containerd/continuity/driver"
13
+	"github.com/containerd/continuity/pathdriver"
14
+
15
+	"github.com/opencontainers/go-digest"
16
+)
17
+
18
+var (
19
+	// ErrNotFound represents the resource not found
20
+	ErrNotFound = fmt.Errorf("not found")
21
+	// ErrNotSupported represents the resource not supported
22
+	ErrNotSupported = fmt.Errorf("not supported")
23
+)
24
+
25
+// Context represents a file system context for accessing resources. The
26
+// responsibility of the context is to convert system specific resources to
27
+// generic Resource objects. Most of this is safe path manipulation, as well
28
+// as extraction of resource details.
29
+type Context interface {
30
+	Apply(Resource) error
31
+	Verify(Resource) error
32
+	Resource(string, os.FileInfo) (Resource, error)
33
+	Walk(filepath.WalkFunc) error
34
+}
35
+
36
+// SymlinkPath is intended to give the symlink target value
37
+// in a root context. Target and linkname are absolute paths
38
+// not under the given root.
39
+type SymlinkPath func(root, linkname, target string) (string, error)
40
+
41
+// ContextOptions represents options to create a new context.
42
+type ContextOptions struct {
43
+	Digester   Digester
44
+	Driver     driverpkg.Driver
45
+	PathDriver pathdriver.PathDriver
46
+	Provider   ContentProvider
47
+}
48
+
49
+// context represents a file system context for accessing resources.
50
+// Generally, all path qualified access and system considerations should land
51
+// here.
52
+type context struct {
53
+	driver     driverpkg.Driver
54
+	pathDriver pathdriver.PathDriver
55
+	root       string
56
+	digester   Digester
57
+	provider   ContentProvider
58
+}
59
+
60
+// NewContext returns a Context associated with root. The default driver will
61
+// be used, as returned by NewDriver.
62
+func NewContext(root string) (Context, error) {
63
+	return NewContextWithOptions(root, ContextOptions{})
64
+}
65
+
66
+// NewContextWithOptions returns a Context associate with the root.
67
+func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
68
+	// normalize to absolute path
69
+	pathDriver := options.PathDriver
70
+	if pathDriver == nil {
71
+		pathDriver = pathdriver.LocalPathDriver
72
+	}
73
+
74
+	root = pathDriver.FromSlash(root)
75
+	root, err := pathDriver.Abs(pathDriver.Clean(root))
76
+	if err != nil {
77
+		return nil, err
78
+	}
79
+
80
+	driver := options.Driver
81
+	if driver == nil {
82
+		driver, err = driverpkg.NewSystemDriver()
83
+		if err != nil {
84
+			return nil, err
85
+		}
86
+	}
87
+
88
+	digester := options.Digester
89
+	if digester == nil {
90
+		digester = simpleDigester{digest.Canonical}
91
+	}
92
+
93
+	// Check the root directory. Need to be a little careful here. We are
94
+	// allowing a link for now, but this may have odd behavior when
95
+	// canonicalizing paths. As long as all files are opened through the link
96
+	// path, this should be okay.
97
+	fi, err := driver.Stat(root)
98
+	if err != nil {
99
+		return nil, err
100
+	}
101
+
102
+	if !fi.IsDir() {
103
+		return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
104
+	}
105
+
106
+	return &context{
107
+		root:       root,
108
+		driver:     driver,
109
+		pathDriver: pathDriver,
110
+		digester:   digester,
111
+		provider:   options.Provider,
112
+	}, nil
113
+}
114
+
115
+// Resource returns the resource as path p, populating the entry with info
116
+// from fi. The path p should be the path of the resource in the context,
117
+// typically obtained through Walk or from the value of Resource.Path(). If fi
118
+// is nil, it will be resolved.
119
+func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
120
+	fp, err := c.fullpath(p)
121
+	if err != nil {
122
+		return nil, err
123
+	}
124
+
125
+	if fi == nil {
126
+		fi, err = c.driver.Lstat(fp)
127
+		if err != nil {
128
+			return nil, err
129
+		}
130
+	}
131
+
132
+	base, err := newBaseResource(p, fi)
133
+	if err != nil {
134
+		return nil, err
135
+	}
136
+
137
+	base.xattrs, err = c.resolveXAttrs(fp, fi, base)
138
+	if err == ErrNotSupported {
139
+		log.Printf("resolving xattrs on %s not supported", fp)
140
+	} else if err != nil {
141
+		return nil, err
142
+	}
143
+
144
+	// TODO(stevvooe): Handle windows alternate data streams.
145
+
146
+	if fi.Mode().IsRegular() {
147
+		dgst, err := c.digest(p)
148
+		if err != nil {
149
+			return nil, err
150
+		}
151
+
152
+		return newRegularFile(*base, base.paths, fi.Size(), dgst)
153
+	}
154
+
155
+	if fi.Mode().IsDir() {
156
+		return newDirectory(*base)
157
+	}
158
+
159
+	if fi.Mode()&os.ModeSymlink != 0 {
160
+		// We handle relative links vs absolute links by including a
161
+		// beginning slash for absolute links. Effectively, the bundle's
162
+		// root is treated as the absolute link anchor.
163
+		target, err := c.driver.Readlink(fp)
164
+		if err != nil {
165
+			return nil, err
166
+		}
167
+
168
+		return newSymLink(*base, target)
169
+	}
170
+
171
+	if fi.Mode()&os.ModeNamedPipe != 0 {
172
+		return newNamedPipe(*base, base.paths)
173
+	}
174
+
175
+	if fi.Mode()&os.ModeDevice != 0 {
176
+		deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
177
+		if !ok {
178
+			log.Printf("device extraction not supported %s", fp)
179
+			return nil, ErrNotSupported
180
+		}
181
+
182
+		// character and block devices merely need to recover the
183
+		// major/minor device number.
184
+		major, minor, err := deviceDriver.DeviceInfo(fi)
185
+		if err != nil {
186
+			return nil, err
187
+		}
188
+
189
+		return newDevice(*base, base.paths, major, minor)
190
+	}
191
+
192
+	log.Printf("%q (%v) is not supported", fp, fi.Mode())
193
+	return nil, ErrNotFound
194
+}
195
+
196
+func (c *context) verifyMetadata(resource, target Resource) error {
197
+	if target.Mode() != resource.Mode() {
198
+		return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
199
+	}
200
+
201
+	if target.UID() != resource.UID() {
202
+		return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
203
+	}
204
+
205
+	if target.GID() != resource.GID() {
206
+		return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
207
+	}
208
+
209
+	if xattrer, ok := resource.(XAttrer); ok {
210
+		txattrer, tok := target.(XAttrer)
211
+		if !tok {
212
+			return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
213
+		}
214
+
215
+		// For xattrs, only ensure that we have those defined in the resource
216
+		// and their values match. We can ignore other xattrs. In other words,
217
+		// we only verify that target has the subset defined by resource.
218
+		txattrs := txattrer.XAttrs()
219
+		for attr, value := range xattrer.XAttrs() {
220
+			tvalue, ok := txattrs[attr]
221
+			if !ok {
222
+				return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
223
+			}
224
+
225
+			if !bytes.Equal(value, tvalue) {
226
+				return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
227
+			}
228
+		}
229
+	}
230
+
231
+	switch r := resource.(type) {
232
+	case RegularFile:
233
+		// TODO(stevvooe): Another reason to use a record-based approach. We
234
+		// have to do another type switch to get this to work. This could be
235
+		// fixed with an Equal function, but let's study this a little more to
236
+		// be sure.
237
+		t, ok := target.(RegularFile)
238
+		if !ok {
239
+			return fmt.Errorf("resource %q target not a regular file", r.Path())
240
+		}
241
+
242
+		if t.Size() != r.Size() {
243
+			return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
244
+		}
245
+	case Directory:
246
+		t, ok := target.(Directory)
247
+		if !ok {
248
+			return fmt.Errorf("resource %q target not a directory", t.Path())
249
+		}
250
+	case SymLink:
251
+		t, ok := target.(SymLink)
252
+		if !ok {
253
+			return fmt.Errorf("resource %q target not a symlink", t.Path())
254
+		}
255
+
256
+		if t.Target() != r.Target() {
257
+			return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
258
+		}
259
+	case Device:
260
+		t, ok := target.(Device)
261
+		if !ok {
262
+			return fmt.Errorf("resource %q is not a device", t.Path())
263
+		}
264
+
265
+		if t.Major() != r.Major() || t.Minor() != r.Minor() {
266
+			return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
267
+		}
268
+	case NamedPipe:
269
+		t, ok := target.(NamedPipe)
270
+		if !ok {
271
+			return fmt.Errorf("resource %q is not a named pipe", t.Path())
272
+		}
273
+	default:
274
+		return fmt.Errorf("cannot verify resource: %v", resource)
275
+	}
276
+
277
+	return nil
278
+}
279
+
280
+// Verify the resource in the context. An error will be returned a discrepancy
281
+// is found.
282
+func (c *context) Verify(resource Resource) error {
283
+	fp, err := c.fullpath(resource.Path())
284
+	if err != nil {
285
+		return err
286
+	}
287
+
288
+	fi, err := c.driver.Lstat(fp)
289
+	if err != nil {
290
+		return err
291
+	}
292
+
293
+	target, err := c.Resource(resource.Path(), fi)
294
+	if err != nil {
295
+		return err
296
+	}
297
+
298
+	if target.Path() != resource.Path() {
299
+		return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
300
+	}
301
+
302
+	if err := c.verifyMetadata(resource, target); err != nil {
303
+		return err
304
+	}
305
+
306
+	if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
307
+		hardlinkKey, err := newHardlinkKey(fi)
308
+		if err == errNotAHardLink {
309
+			if len(h.Paths()) > 1 {
310
+				return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
311
+			}
312
+		} else if err != nil {
313
+			return err
314
+		}
315
+
316
+		for _, path := range h.Paths()[1:] {
317
+			fpLink, err := c.fullpath(path)
318
+			if err != nil {
319
+				return err
320
+			}
321
+
322
+			fiLink, err := c.driver.Lstat(fpLink)
323
+			if err != nil {
324
+				return err
325
+			}
326
+
327
+			targetLink, err := c.Resource(path, fiLink)
328
+			if err != nil {
329
+				return err
330
+			}
331
+
332
+			hardlinkKeyLink, err := newHardlinkKey(fiLink)
333
+			if err != nil {
334
+				return err
335
+			}
336
+
337
+			if hardlinkKeyLink != hardlinkKey {
338
+				return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
339
+			}
340
+
341
+			if err := c.verifyMetadata(resource, targetLink); err != nil {
342
+				return err
343
+			}
344
+		}
345
+	}
346
+
347
+	switch r := resource.(type) {
348
+	case RegularFile:
349
+		t, ok := target.(RegularFile)
350
+		if !ok {
351
+			return fmt.Errorf("resource %q target not a regular file", r.Path())
352
+		}
353
+
354
+		// TODO(stevvooe): This may need to get a little more sophisticated
355
+		// for digest comparison. We may want to actually calculate the
356
+		// provided digests, rather than the implementations having an
357
+		// overlap.
358
+		if !digestsMatch(t.Digests(), r.Digests()) {
359
+			return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
360
+		}
361
+	}
362
+
363
+	return nil
364
+}
365
+
366
+func (c *context) checkoutFile(fp string, rf RegularFile) error {
367
+	if c.provider == nil {
368
+		return fmt.Errorf("no file provider")
369
+	}
370
+	var (
371
+		r   io.ReadCloser
372
+		err error
373
+	)
374
+	for _, dgst := range rf.Digests() {
375
+		r, err = c.provider.Reader(dgst)
376
+		if err == nil {
377
+			break
378
+		}
379
+	}
380
+	if err != nil {
381
+		return fmt.Errorf("file content could not be provided: %v", err)
382
+	}
383
+	defer r.Close()
384
+
385
+	return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
386
+}
387
+
388
+// Apply the resource to the contexts. An error will be returned if the
389
+// operation fails. Depending on the resource type, the resource may be
390
+// created. For resource that cannot be resolved, an error will be returned.
391
+func (c *context) Apply(resource Resource) error {
392
+	fp, err := c.fullpath(resource.Path())
393
+	if err != nil {
394
+		return err
395
+	}
396
+
397
+	if !strings.HasPrefix(fp, c.root) {
398
+		return fmt.Errorf("resource %v escapes root", resource)
399
+	}
400
+
401
+	var chmod = true
402
+	fi, err := c.driver.Lstat(fp)
403
+	if err != nil {
404
+		if !os.IsNotExist(err) {
405
+			return err
406
+		}
407
+	}
408
+
409
+	switch r := resource.(type) {
410
+	case RegularFile:
411
+		if fi == nil {
412
+			if err := c.checkoutFile(fp, r); err != nil {
413
+				return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
414
+			}
415
+			chmod = false
416
+		} else {
417
+			if !fi.Mode().IsRegular() {
418
+				return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
419
+			}
420
+			if fi.Size() != r.Size() {
421
+				if err := c.checkoutFile(fp, r); err != nil {
422
+					return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
423
+				}
424
+			} else {
425
+				for _, dgst := range r.Digests() {
426
+					f, err := os.Open(fp)
427
+					if err != nil {
428
+						return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
429
+					}
430
+					compared, err := dgst.Algorithm().FromReader(f)
431
+					if err == nil && dgst != compared {
432
+						if err := c.checkoutFile(fp, r); err != nil {
433
+							return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
434
+						}
435
+						break
436
+					}
437
+					if err1 := f.Close(); err == nil {
438
+						err = err1
439
+					}
440
+					if err != nil {
441
+						return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
442
+					}
443
+				}
444
+			}
445
+		}
446
+	case Directory:
447
+		if fi == nil {
448
+			if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
449
+				return err
450
+			}
451
+		} else if !fi.Mode().IsDir() {
452
+			return fmt.Errorf("%q should be a directory, but is not", resource.Path())
453
+		}
454
+
455
+	case SymLink:
456
+		var target string // only possibly set if target resource is a symlink
457
+
458
+		if fi != nil {
459
+			if fi.Mode()&os.ModeSymlink != 0 {
460
+				target, err = c.driver.Readlink(fp)
461
+				if err != nil {
462
+					return err
463
+				}
464
+			}
465
+		}
466
+
467
+		if target != r.Target() {
468
+			if fi != nil {
469
+				if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
470
+					return err
471
+				}
472
+			}
473
+
474
+			if err := c.driver.Symlink(r.Target(), fp); err != nil {
475
+				return err
476
+			}
477
+		}
478
+
479
+	case Device:
480
+		if fi == nil {
481
+			if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
482
+				return err
483
+			}
484
+		} else if (fi.Mode() & os.ModeDevice) == 0 {
485
+			return fmt.Errorf("%q should be a device, but is not", resource.Path())
486
+		} else {
487
+			major, minor, err := devices.DeviceInfo(fi)
488
+			if err != nil {
489
+				return err
490
+			}
491
+			if major != r.Major() || minor != r.Minor() {
492
+				if err := c.driver.Remove(fp); err != nil {
493
+					return err
494
+				}
495
+
496
+				if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
497
+					return err
498
+				}
499
+			}
500
+		}
501
+
502
+	case NamedPipe:
503
+		if fi == nil {
504
+			if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
505
+				return err
506
+			}
507
+		} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
508
+			return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
509
+		}
510
+	}
511
+
512
+	if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
513
+		for _, path := range h.Paths() {
514
+			if path == resource.Path() {
515
+				continue
516
+			}
517
+
518
+			lp, err := c.fullpath(path)
519
+			if err != nil {
520
+				return err
521
+			}
522
+
523
+			if _, fi := c.driver.Lstat(lp); fi == nil {
524
+				c.driver.Remove(lp)
525
+			}
526
+			if err := c.driver.Link(fp, lp); err != nil {
527
+				return err
528
+			}
529
+		}
530
+	}
531
+
532
+	// Update filemode if file was not created
533
+	if chmod {
534
+		if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
535
+			return err
536
+		}
537
+	}
538
+
539
+	if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
540
+		return err
541
+	}
542
+
543
+	if xattrer, ok := resource.(XAttrer); ok {
544
+		// For xattrs, only ensure that we have those defined in the resource
545
+		// and their values are set. We can ignore other xattrs. In other words,
546
+		// we only set xattres defined by resource but never remove.
547
+
548
+		if _, ok := resource.(SymLink); ok {
549
+			lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
550
+			if !ok {
551
+				return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
552
+			}
553
+			if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
554
+				return err
555
+			}
556
+		} else {
557
+			xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
558
+			if !ok {
559
+				return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
560
+			}
561
+			if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
562
+				return err
563
+			}
564
+		}
565
+	}
566
+
567
+	return nil
568
+}
569
+
570
+// Walk provides a convenience function to call filepath.Walk correctly for
571
+// the context. Otherwise identical to filepath.Walk, the path argument is
572
+// corrected to be contained within the context.
573
+func (c *context) Walk(fn filepath.WalkFunc) error {
574
+	root := c.root
575
+	fi, err := c.driver.Lstat(c.root)
576
+	if err == nil && fi.Mode()&os.ModeSymlink != 0 {
577
+		root, err = c.driver.Readlink(c.root)
578
+		if err != nil {
579
+			return err
580
+		}
581
+	}
582
+	return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
583
+		contained, err := c.containWithRoot(p, root)
584
+		return fn(contained, fi, err)
585
+	})
586
+}
587
+
588
+// fullpath returns the system path for the resource, joined with the context
589
+// root. The path p must be a part of the context.
590
+func (c *context) fullpath(p string) (string, error) {
591
+	p = c.pathDriver.Join(c.root, p)
592
+	if !strings.HasPrefix(p, c.root) {
593
+		return "", fmt.Errorf("invalid context path")
594
+	}
595
+
596
+	return p, nil
597
+}
598
+
599
+// contain cleans and santizes the filesystem path p to be an absolute path,
600
+// effectively relative to the context root.
601
+func (c *context) contain(p string) (string, error) {
602
+	return c.containWithRoot(p, c.root)
603
+}
604
+
605
+// containWithRoot cleans and santizes the filesystem path p to be an absolute path,
606
+// effectively relative to the passed root. Extra care should be used when calling this
607
+// instead of contain. This is needed for Walk, as if context root is a symlink,
608
+// it must be evaluated prior to the Walk
609
+func (c *context) containWithRoot(p string, root string) (string, error) {
610
+	sanitized, err := c.pathDriver.Rel(root, p)
611
+	if err != nil {
612
+		return "", err
613
+	}
614
+
615
+	// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
616
+	// "containment error", so the caller can decide what to do.
617
+	return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
618
+}
619
+
620
+// digest returns the digest of the file at path p, relative to the root.
621
+func (c *context) digest(p string) (digest.Digest, error) {
622
+	f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
623
+	if err != nil {
624
+		return "", err
625
+	}
626
+	defer f.Close()
627
+
628
+	return c.digester.Digest(f)
629
+}
630
+
631
+// resolveXAttrs attempts to resolve the extended attributes for the resource
632
+// at the path fp, which is the full path to the resource. If the resource
633
+// cannot have xattrs, nil will be returned.
634
+func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
635
+	if fi.Mode().IsRegular() || fi.Mode().IsDir() {
636
+		xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
637
+		if !ok {
638
+			log.Println("xattr extraction not supported")
639
+			return nil, ErrNotSupported
640
+		}
641
+
642
+		return xattrDriver.Getxattr(fp)
643
+	}
644
+
645
+	if fi.Mode()&os.ModeSymlink != 0 {
646
+		lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
647
+		if !ok {
648
+			log.Println("xattr extraction for symlinks not supported")
649
+			return nil, ErrNotSupported
650
+		}
651
+
652
+		return lxattrDriver.LGetxattr(fp)
653
+	}
654
+
655
+	return nil, nil
656
+}
0 657
new file mode 100644
... ...
@@ -0,0 +1,88 @@
0
+package continuity
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"sort"
6
+
7
+	"github.com/opencontainers/go-digest"
8
+)
9
+
10
+// Digester produces a digest for a given read stream
11
+type Digester interface {
12
+	Digest(io.Reader) (digest.Digest, error)
13
+}
14
+
15
+// ContentProvider produces a read stream for a given digest
16
+type ContentProvider interface {
17
+	Reader(digest.Digest) (io.ReadCloser, error)
18
+}
19
+
20
+type simpleDigester struct {
21
+	algorithm digest.Algorithm
22
+}
23
+
24
+func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
25
+	digester := sd.algorithm.Digester()
26
+
27
+	if _, err := io.Copy(digester.Hash(), r); err != nil {
28
+		return "", err
29
+	}
30
+
31
+	return digester.Digest(), nil
32
+}
33
+
34
+// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the
35
+// digests are not repeated and no two digests with the same algorithm have
36
+// different values. Because a stable sort is used, this has the effect of
37
+// "zipping" digest collections from multiple resources.
38
+func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
39
+	sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here.
40
+	seen := map[digest.Digest]struct{}{}
41
+	algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
42
+
43
+	var out []digest.Digest
44
+	// uniqify the digests
45
+	for _, d := range digests {
46
+		if _, ok := seen[d]; ok {
47
+			continue
48
+		}
49
+
50
+		seen[d] = struct{}{}
51
+		algs[d.Algorithm()] = append(algs[d.Algorithm()], d)
52
+
53
+		if len(algs[d.Algorithm()]) > 1 {
54
+			return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
55
+		}
56
+
57
+		out = append(out, d)
58
+	}
59
+
60
+	return out, nil
61
+}
62
+
63
+// digestsMatch compares the two sets of digests to see if they match.
64
+func digestsMatch(as, bs []digest.Digest) bool {
65
+	all := append(as, bs...)
66
+
67
+	uniqified, err := uniqifyDigests(all...)
68
+	if err != nil {
69
+		// the only error uniqifyDigests returns is when the digests disagree.
70
+		return false
71
+	}
72
+
73
+	disjoint := len(as) + len(bs)
74
+	if len(uniqified) == disjoint {
75
+		// if these two sets have the same cardinality, we know both sides
76
+		// didn't share any digests.
77
+		return false
78
+	}
79
+
80
+	return true
81
+}
82
+
83
+type digestSlice []digest.Digest
84
+
85
+func (p digestSlice) Len() int           { return len(p) }
86
+func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
87
+func (p digestSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
0 88
new file mode 100644
... ...
@@ -0,0 +1,113 @@
0
+package continuity
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"io"
6
+	"os"
7
+	"strconv"
8
+	"strings"
9
+)
10
+
11
+// TODO(stevvooe): This needs a lot of work before we can call it useful.
12
+
13
+type groupIndex struct {
14
+	byName map[string]*group
15
+	byGID  map[int]*group
16
+}
17
+
18
+func getGroupIndex() (*groupIndex, error) {
19
+	f, err := os.Open("/etc/group")
20
+	if err != nil {
21
+		return nil, err
22
+	}
23
+	defer f.Close()
24
+
25
+	groups, err := parseGroups(f)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+
30
+	return newGroupIndex(groups), nil
31
+}
32
+
33
+func newGroupIndex(groups []group) *groupIndex {
34
+	gi := &groupIndex{
35
+		byName: make(map[string]*group),
36
+		byGID:  make(map[int]*group),
37
+	}
38
+
39
+	for i, group := range groups {
40
+		gi.byGID[group.gid] = &groups[i]
41
+		gi.byName[group.name] = &groups[i]
42
+	}
43
+
44
+	return gi
45
+}
46
+
47
+type group struct {
48
+	name    string
49
+	gid     int
50
+	members []string
51
+}
52
+
53
+func getGroupName(gid int) (string, error) {
54
+	f, err := os.Open("/etc/group")
55
+	if err != nil {
56
+		return "", err
57
+	}
58
+	defer f.Close()
59
+
60
+	groups, err := parseGroups(f)
61
+	if err != nil {
62
+		return "", err
63
+	}
64
+
65
+	for _, group := range groups {
66
+		if group.gid == gid {
67
+			return group.name, nil
68
+		}
69
+	}
70
+
71
+	return "", fmt.Errorf("no group for gid")
72
+}
73
+
74
+// parseGroups parses an /etc/group file for group names, ids and membership.
75
+// This is unix specific.
76
+func parseGroups(rd io.Reader) ([]group, error) {
77
+	var groups []group
78
+	scanner := bufio.NewScanner(rd)
79
+
80
+	for scanner.Scan() {
81
+		if strings.HasPrefix(scanner.Text(), "#") {
82
+			continue // skip comment
83
+		}
84
+
85
+		parts := strings.SplitN(scanner.Text(), ":", 4)
86
+
87
+		if len(parts) != 4 {
88
+			return nil, fmt.Errorf("bad entry: %q", scanner.Text())
89
+		}
90
+
91
+		name, _, sgid, smembers := parts[0], parts[1], parts[2], parts[3]
92
+
93
+		gid, err := strconv.Atoi(sgid)
94
+		if err != nil {
95
+			return nil, fmt.Errorf("bad gid: %q", gid)
96
+		}
97
+
98
+		members := strings.Split(smembers, ",")
99
+
100
+		groups = append(groups, group{
101
+			name:    name,
102
+			gid:     gid,
103
+			members: members,
104
+		})
105
+	}
106
+
107
+	if scanner.Err() != nil {
108
+		return nil, scanner.Err()
109
+	}
110
+
111
+	return groups, nil
112
+}
0 113
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package continuity
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+)
6
+
7
+var (
8
+	errNotAHardLink = fmt.Errorf("invalid hardlink")
9
+)
10
+
11
+type hardlinkManager struct {
12
+	hardlinks map[hardlinkKey][]Resource
13
+}
14
+
15
+func newHardlinkManager() *hardlinkManager {
16
+	return &hardlinkManager{
17
+		hardlinks: map[hardlinkKey][]Resource{},
18
+	}
19
+}
20
+
21
+// Add attempts to add the resource to the hardlink manager. If the resource
22
+// cannot be considered as a hardlink candidate, errNotAHardLink is returned.
23
+func (hlm *hardlinkManager) Add(fi os.FileInfo, resource Resource) error {
24
+	if _, ok := resource.(Hardlinkable); !ok {
25
+		return errNotAHardLink
26
+	}
27
+
28
+	key, err := newHardlinkKey(fi)
29
+	if err != nil {
30
+		return err
31
+	}
32
+
33
+	hlm.hardlinks[key] = append(hlm.hardlinks[key], resource)
34
+
35
+	return nil
36
+}
37
+
38
+// Merge processes the current state of the hardlink manager and merges any
39
+// shared nodes into hardlinked resources.
40
+func (hlm *hardlinkManager) Merge() ([]Resource, error) {
41
+	var resources []Resource
42
+	for key, linked := range hlm.hardlinks {
43
+		if len(linked) < 1 {
44
+			return nil, fmt.Errorf("no hardlink entrys for dev, inode pair: %#v", key)
45
+		}
46
+
47
+		merged, err := Merge(linked...)
48
+		if err != nil {
49
+			return nil, fmt.Errorf("error merging hardlink: %v", err)
50
+		}
51
+
52
+		resources = append(resources, merged)
53
+	}
54
+
55
+	return resources, nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+// +build linux darwin freebsd solaris
1
+
2
+package continuity
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"syscall"
8
+)
9
+
10
+// hardlinkKey provides a tuple-key for managing hardlinks. This is system-
11
+// specific.
12
+type hardlinkKey struct {
13
+	dev   uint64
14
+	inode uint64
15
+}
16
+
17
+// newHardlinkKey returns a hardlink key for the provided file info. If the
18
+// resource does not represent a possible hardlink, errNotAHardLink will be
19
+// returned.
20
+func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
21
+	sys, ok := fi.Sys().(*syscall.Stat_t)
22
+	if !ok {
23
+		return hardlinkKey{}, fmt.Errorf("cannot resolve (*syscall.Stat_t) from os.FileInfo")
24
+	}
25
+
26
+	if sys.Nlink < 2 {
27
+		// NOTE(stevvooe): This is not always true for all filesystems. We
28
+		// should somehow detect this and provided a slow "polyfill" that
29
+		// leverages os.SameFile if we detect a filesystem where link counts
30
+		// is not really supported.
31
+		return hardlinkKey{}, errNotAHardLink
32
+	}
33
+
34
+	return hardlinkKey{dev: uint64(sys.Dev), inode: uint64(sys.Ino)}, nil
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package continuity
1
+
2
+import "os"
3
+
4
+type hardlinkKey struct{}
5
+
6
+func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
7
+	// NOTE(stevvooe): Obviously, this is not yet implemented. However, the
8
+	// makings of an implementation are available in src/os/types_windows.go. More
9
+	// investigation needs to be done to figure out exactly how to do this.
10
+	return hardlinkKey{}, errNotAHardLink
11
+}
0 12
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package continuity
1
+
2
+import (
3
+	"bytes"
4
+	"io"
5
+	"io/ioutil"
6
+	"os"
7
+	"path/filepath"
8
+)
9
+
10
+// AtomicWriteFile atomically writes data to a file by first writing to a
11
+// temp file and calling rename.
12
+func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
13
+	buf := bytes.NewBuffer(data)
14
+	return atomicWriteFile(filename, buf, int64(len(data)), perm)
15
+}
16
+
17
+// atomicWriteFile writes data to a file by first writing to a temp
18
+// file and calling rename.
19
+func atomicWriteFile(filename string, r io.Reader, dataSize int64, perm os.FileMode) error {
20
+	f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
21
+	if err != nil {
22
+		return err
23
+	}
24
+	err = os.Chmod(f.Name(), perm)
25
+	if err != nil {
26
+		f.Close()
27
+		return err
28
+	}
29
+	n, err := io.Copy(f, r)
30
+	if err == nil && n < dataSize {
31
+		f.Close()
32
+		return io.ErrShortWrite
33
+	}
34
+	if err != nil {
35
+		f.Close()
36
+		return err
37
+	}
38
+	if err := f.Sync(); err != nil {
39
+		f.Close()
40
+		return err
41
+	}
42
+	if err := f.Close(); err != nil {
43
+		return err
44
+	}
45
+	return os.Rename(f.Name(), filename)
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,144 @@
0
+package continuity
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"log"
6
+	"os"
7
+	"sort"
8
+
9
+	pb "github.com/containerd/continuity/proto"
10
+	"github.com/golang/protobuf/proto"
11
+)
12
+
13
+// Manifest provides the contents of a manifest. Users of this struct should
14
+// not typically modify any fields directly.
15
+type Manifest struct {
16
+	// Resources specifies all the resources for a manifest in order by path.
17
+	Resources []Resource
18
+}
19
+
20
+func Unmarshal(p []byte) (*Manifest, error) {
21
+	var bm pb.Manifest
22
+
23
+	if err := proto.Unmarshal(p, &bm); err != nil {
24
+		return nil, err
25
+	}
26
+
27
+	var m Manifest
28
+	for _, b := range bm.Resource {
29
+		r, err := fromProto(b)
30
+		if err != nil {
31
+			return nil, err
32
+		}
33
+
34
+		m.Resources = append(m.Resources, r)
35
+	}
36
+
37
+	return &m, nil
38
+}
39
+
40
+func Marshal(m *Manifest) ([]byte, error) {
41
+	var bm pb.Manifest
42
+	for _, resource := range m.Resources {
43
+		bm.Resource = append(bm.Resource, toProto(resource))
44
+	}
45
+
46
+	return proto.Marshal(&bm)
47
+}
48
+
49
+func MarshalText(w io.Writer, m *Manifest) error {
50
+	var bm pb.Manifest
51
+	for _, resource := range m.Resources {
52
+		bm.Resource = append(bm.Resource, toProto(resource))
53
+	}
54
+
55
+	return proto.MarshalText(w, &bm)
56
+}
57
+
58
+// BuildManifest creates the manifest for the given context
59
+func BuildManifest(ctx Context) (*Manifest, error) {
60
+	resourcesByPath := map[string]Resource{}
61
+	hardlinks := newHardlinkManager()
62
+
63
+	if err := ctx.Walk(func(p string, fi os.FileInfo, err error) error {
64
+		if err != nil {
65
+			return fmt.Errorf("error walking %s: %v", p, err)
66
+		}
67
+
68
+		if p == string(os.PathSeparator) {
69
+			// skip root
70
+			return nil
71
+		}
72
+
73
+		resource, err := ctx.Resource(p, fi)
74
+		if err != nil {
75
+			if err == ErrNotFound {
76
+				return nil
77
+			}
78
+			log.Printf("error getting resource %q: %v", p, err)
79
+			return err
80
+		}
81
+
82
+		// add to the hardlink manager
83
+		if err := hardlinks.Add(fi, resource); err == nil {
84
+			// Resource has been accepted by hardlink manager so we don't add
85
+			// it to the resourcesByPath until we merge at the end.
86
+			return nil
87
+		} else if err != errNotAHardLink {
88
+			// handle any other case where we have a proper error.
89
+			return fmt.Errorf("adding hardlink %s: %v", p, err)
90
+		}
91
+
92
+		resourcesByPath[p] = resource
93
+
94
+		return nil
95
+	}); err != nil {
96
+		return nil, err
97
+	}
98
+
99
+	// merge and post-process the hardlinks.
100
+	hardlinked, err := hardlinks.Merge()
101
+	if err != nil {
102
+		return nil, err
103
+	}
104
+
105
+	for _, resource := range hardlinked {
106
+		resourcesByPath[resource.Path()] = resource
107
+	}
108
+
109
+	var resources []Resource
110
+	for _, resource := range resourcesByPath {
111
+		resources = append(resources, resource)
112
+	}
113
+
114
+	sort.Stable(ByPath(resources))
115
+
116
+	return &Manifest{
117
+		Resources: resources,
118
+	}, nil
119
+}
120
+
121
+// VerifyManifest verifies all the resources in a manifest
122
+// against files from the given context.
123
+func VerifyManifest(ctx Context, manifest *Manifest) error {
124
+	for _, resource := range manifest.Resources {
125
+		if err := ctx.Verify(resource); err != nil {
126
+			return err
127
+		}
128
+	}
129
+
130
+	return nil
131
+}
132
+
133
+// ApplyManifest applies on the resources in a manifest to
134
+// the given context.
135
+func ApplyManifest(ctx Context, manifest *Manifest) error {
136
+	for _, resource := range manifest.Resources {
137
+		if err := ctx.Apply(resource); err != nil {
138
+			return err
139
+		}
140
+	}
141
+
142
+	return nil
143
+}
0 144
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+package proto
1
+
2
+//go:generate protoc --go_out=. manifest.proto
0 3
new file mode 100644
... ...
@@ -0,0 +1,181 @@
0
+// Code generated by protoc-gen-go.
1
+// source: manifest.proto
2
+// DO NOT EDIT!
3
+
4
+/*
5
+Package proto is a generated protocol buffer package.
6
+
7
+It is generated from these files:
8
+	manifest.proto
9
+
10
+It has these top-level messages:
11
+	Manifest
12
+	Resource
13
+	XAttr
14
+	ADSEntry
15
+*/
16
+package proto
17
+
18
+import proto1 "github.com/golang/protobuf/proto"
19
+import fmt "fmt"
20
+import math "math"
21
+
22
+// Reference imports to suppress errors if they are not otherwise used.
23
+var _ = proto1.Marshal
24
+var _ = fmt.Errorf
25
+var _ = math.Inf
26
+
27
+// This is a compile-time assertion to ensure that this generated file
28
+// is compatible with the proto package it is being compiled against.
29
+// A compilation error at this line likely means your copy of the
30
+// proto package needs to be updated.
31
+const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
32
+
33
+// Manifest specifies the entries in a container bundle, keyed and sorted by
34
+// path.
35
+type Manifest struct {
36
+	Resource []*Resource `protobuf:"bytes,1,rep,name=resource" json:"resource,omitempty"`
37
+}
38
+
39
+func (m *Manifest) Reset()                    { *m = Manifest{} }
40
+func (m *Manifest) String() string            { return proto1.CompactTextString(m) }
41
+func (*Manifest) ProtoMessage()               {}
42
+func (*Manifest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
43
+
44
+func (m *Manifest) GetResource() []*Resource {
45
+	if m != nil {
46
+		return m.Resource
47
+	}
48
+	return nil
49
+}
50
+
51
+type Resource struct {
52
+	// Path specifies the path from the bundle root. If more than one
53
+	// path is present, the entry may represent a hardlink, rather than using
54
+	// a link target. The path format is operating system specific.
55
+	Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"`
56
+	// Uid specifies the user id for the resource.
57
+	Uid int64 `protobuf:"varint,2,opt,name=uid" json:"uid,omitempty"`
58
+	// Gid specifies the group id for the resource.
59
+	Gid int64 `protobuf:"varint,3,opt,name=gid" json:"gid,omitempty"`
60
+	// user and group are not currently used but their field numbers have been
61
+	// reserved for future use. As such, they are marked as deprecated.
62
+	User  string `protobuf:"bytes,4,opt,name=user" json:"user,omitempty"`
63
+	Group string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
64
+	// Mode defines the file mode and permissions. We've used the same
65
+	// bit-packing from Go's os package,
66
+	// http://golang.org/pkg/os/#FileMode, since they've done the work of
67
+	// creating a cross-platform layout.
68
+	Mode uint32 `protobuf:"varint,6,opt,name=mode" json:"mode,omitempty"`
69
+	// Size specifies the size in bytes of the resource. This is only valid
70
+	// for regular files.
71
+	Size uint64 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"`
72
+	// Digest specifies the content digest of the target file. Only valid for
73
+	// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
74
+	// For detailed information about the format, please refer to OCI Image Spec:
75
+	// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
76
+	// The digests are sorted in lexical order and implementations may choose
77
+	// which algorithms they prefer.
78
+	Digest []string `protobuf:"bytes,8,rep,name=digest" json:"digest,omitempty"`
79
+	// Target defines the target of a hard or soft link. Absolute links start
80
+	// with a slash and specify the resource relative to the bundle root.
81
+	// Relative links do not start with a slash and are relative to the
82
+	// resource path.
83
+	Target string `protobuf:"bytes,9,opt,name=target" json:"target,omitempty"`
84
+	// Major specifies the major device number for character and block devices.
85
+	Major uint64 `protobuf:"varint,10,opt,name=major" json:"major,omitempty"`
86
+	// Minor specifies the minor device number for character and block devices.
87
+	Minor uint64 `protobuf:"varint,11,opt,name=minor" json:"minor,omitempty"`
88
+	// Xattr provides storage for extended attributes for the target resource.
89
+	Xattr []*XAttr `protobuf:"bytes,12,rep,name=xattr" json:"xattr,omitempty"`
90
+	// Ads stores one or more alternate data streams for the target resource.
91
+	Ads []*ADSEntry `protobuf:"bytes,13,rep,name=ads" json:"ads,omitempty"`
92
+}
93
+
94
+func (m *Resource) Reset()                    { *m = Resource{} }
95
+func (m *Resource) String() string            { return proto1.CompactTextString(m) }
96
+func (*Resource) ProtoMessage()               {}
97
+func (*Resource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
98
+
99
+func (m *Resource) GetXattr() []*XAttr {
100
+	if m != nil {
101
+		return m.Xattr
102
+	}
103
+	return nil
104
+}
105
+
106
+func (m *Resource) GetAds() []*ADSEntry {
107
+	if m != nil {
108
+		return m.Ads
109
+	}
110
+	return nil
111
+}
112
+
113
+// XAttr encodes extended attributes for a resource.
114
+type XAttr struct {
115
+	// Name specifies the attribute name.
116
+	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
117
+	// Data specifies the associated data for the attribute.
118
+	Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
119
+}
120
+
121
+func (m *XAttr) Reset()                    { *m = XAttr{} }
122
+func (m *XAttr) String() string            { return proto1.CompactTextString(m) }
123
+func (*XAttr) ProtoMessage()               {}
124
+func (*XAttr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
125
+
126
+// ADSEntry encodes information for a Windows Alternate Data Stream.
127
+type ADSEntry struct {
128
+	// Name specifices the stream name.
129
+	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
130
+	// Data specifies the stream data.
131
+	// See also the description about the digest below.
132
+	Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
133
+	// Digest is a CAS representation of the stream data.
134
+	//
135
+	// At least one of data or digest MUST be specified, and either one of them
136
+	// SHOULD be specified.
137
+	//
138
+	// How to access the actual data using the digest is implementation-specific,
139
+	// and implementations can choose not to implement digest.
140
+	// So, digest SHOULD be used only when the stream data is large.
141
+	Digest string `protobuf:"bytes,3,opt,name=digest" json:"digest,omitempty"`
142
+}
143
+
144
+func (m *ADSEntry) Reset()                    { *m = ADSEntry{} }
145
+func (m *ADSEntry) String() string            { return proto1.CompactTextString(m) }
146
+func (*ADSEntry) ProtoMessage()               {}
147
+func (*ADSEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
148
+
149
+func init() {
150
+	proto1.RegisterType((*Manifest)(nil), "proto.Manifest")
151
+	proto1.RegisterType((*Resource)(nil), "proto.Resource")
152
+	proto1.RegisterType((*XAttr)(nil), "proto.XAttr")
153
+	proto1.RegisterType((*ADSEntry)(nil), "proto.ADSEntry")
154
+}
155
+
156
+func init() { proto1.RegisterFile("manifest.proto", fileDescriptor0) }
157
+
158
+var fileDescriptor0 = []byte{
159
+	// 317 bytes of a gzipped FileDescriptorProto
160
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0x4f, 0x4b, 0xf3, 0x40,
161
+	0x10, 0xc6, 0x49, 0x93, 0xf4, 0x4d, 0xa7, 0xed, 0xab, 0x2c, 0x52, 0xe6, 0x18, 0x73, 0x0a, 0x08,
162
+	0x15, 0xf4, 0xe0, 0xb9, 0xa2, 0x17, 0xc1, 0xcb, 0x7a, 0xf1, 0xba, 0xba, 0x6b, 0x5c, 0x21, 0xd9,
163
+	0xb0, 0xd9, 0x80, 0xfa, 0xe5, 0xfc, 0x6a, 0x32, 0xb3, 0x69, 0xd1, 0x9b, 0xa7, 0x3c, 0xcf, 0x6f,
164
+	0xfe, 0x64, 0xf6, 0x81, 0xff, 0xad, 0xea, 0xec, 0x8b, 0x19, 0xc2, 0xb6, 0xf7, 0x2e, 0x38, 0x91,
165
+	0xf3, 0xa7, 0xba, 0x82, 0xe2, 0x7e, 0x2a, 0x88, 0x33, 0x28, 0xbc, 0x19, 0xdc, 0xe8, 0x9f, 0x0d,
166
+	0x26, 0x65, 0x5a, 0x2f, 0x2f, 0x8e, 0x62, 0xf3, 0x56, 0x4e, 0x58, 0x1e, 0x1a, 0xaa, 0xaf, 0x19,
167
+	0x14, 0x7b, 0x2c, 0x04, 0x64, 0xbd, 0x0a, 0xaf, 0x3c, 0xb5, 0x90, 0xac, 0xc5, 0x31, 0xa4, 0xa3,
168
+	0xd5, 0x38, 0x2b, 0x93, 0x3a, 0x95, 0x24, 0x89, 0x34, 0x56, 0x63, 0x1a, 0x49, 0x63, 0xb5, 0xd8,
169
+	0x40, 0x36, 0x0e, 0xc6, 0x63, 0x56, 0x26, 0xf5, 0xe2, 0x7a, 0x86, 0x89, 0x64, 0x2f, 0x10, 0xf2,
170
+	0xc6, 0xbb, 0xb1, 0xc7, 0xfc, 0x50, 0x88, 0x80, 0xfe, 0xd4, 0x3a, 0x6d, 0x70, 0x5e, 0x26, 0xf5,
171
+	0x5a, 0xb2, 0x26, 0x36, 0xd8, 0x4f, 0x83, 0xff, 0xca, 0xa4, 0xce, 0x24, 0x6b, 0xb1, 0x81, 0xb9,
172
+	0xb6, 0x8d, 0x19, 0x02, 0x16, 0x7c, 0xd3, 0xe4, 0x88, 0x07, 0xe5, 0x1b, 0x13, 0x70, 0x41, 0xab,
173
+	0xe5, 0xe4, 0xc4, 0x09, 0xe4, 0xad, 0x7a, 0x73, 0x1e, 0x81, 0x97, 0x44, 0xc3, 0xd4, 0x76, 0xce,
174
+	0xe3, 0x72, 0xa2, 0x64, 0x44, 0x05, 0xf9, 0xbb, 0x0a, 0xc1, 0xe3, 0x8a, 0x43, 0x5a, 0x4d, 0x21,
175
+	0x3d, 0xee, 0x42, 0xf0, 0x32, 0x96, 0xc4, 0x29, 0xa4, 0x4a, 0x0f, 0xb8, 0xfe, 0x15, 0xe3, 0xee,
176
+	0xe6, 0xe1, 0xb6, 0x0b, 0xfe, 0x43, 0x52, 0xad, 0x3a, 0x87, 0x9c, 0x47, 0xe8, 0xfe, 0x4e, 0xb5,
177
+	0x94, 0x39, 0x5d, 0xc4, 0x9a, 0x98, 0x56, 0x41, 0x71, 0x7c, 0x2b, 0xc9, 0xba, 0xba, 0x83, 0x62,
178
+	0xbf, 0xe1, 0xaf, 0x33, 0x3f, 0x72, 0x48, 0xe3, 0x7b, 0xa3, 0x7b, 0x9a, 0xf3, 0x45, 0x97, 0xdf,
179
+	0x01, 0x00, 0x00, 0xff, 0xff, 0xef, 0x27, 0x99, 0xf7, 0x17, 0x02, 0x00, 0x00,
180
+}
0 181
new file mode 100644
... ...
@@ -0,0 +1,97 @@
0
+syntax = "proto3";
1
+
2
+package proto;
3
+
4
+// Manifest specifies the entries in a container bundle, keyed and sorted by
5
+// path.
6
+message Manifest {
7
+    repeated Resource resource = 1;
8
+}
9
+
10
+message Resource {
11
+    // Path specifies the path from the bundle root. If more than one
12
+    // path is present, the entry may represent a hardlink, rather than using
13
+    // a link target. The path format is operating system specific.
14
+    repeated string path = 1;
15
+
16
+    // NOTE(stevvooe): Need to define clear precedence for user/group/uid/gid precedence.
17
+
18
+    // Uid specifies the user id for the resource.
19
+    int64 uid = 2;
20
+
21
+    // Gid specifies the group id for the resource.
22
+    int64 gid = 3;
23
+
24
+    // user and group are not currently used but their field numbers have been
25
+    // reserved for future use. As such, they are marked as deprecated.
26
+    string user = 4 [deprecated=true]; // "deprecated" stands for "reserved" here
27
+    string group = 5 [deprecated=true]; // "deprecated" stands for "reserved" here
28
+
29
+    // Mode defines the file mode and permissions. We've used the same
30
+    // bit-packing from Go's os package,
31
+    // http://golang.org/pkg/os/#FileMode, since they've done the work of
32
+    // creating a cross-platform layout.
33
+    uint32 mode = 6;
34
+
35
+    // NOTE(stevvooe): Beyond here, we start defining type specific fields.
36
+
37
+    // Size specifies the size in bytes of the resource. This is only valid
38
+    // for regular files.
39
+    uint64 size = 7;
40
+
41
+    // Digest specifies the content digest of the target file. Only valid for
42
+    // regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
43
+    // For detailed information about the format, please refer to OCI Image Spec:
44
+    // https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
45
+    // The digests are sorted in lexical order and implementations may choose
46
+    // which algorithms they prefer.
47
+    repeated string digest = 8;
48
+
49
+    // Target defines the target of a hard or soft link. Absolute links start
50
+    // with a slash and specify the resource relative to the bundle root.
51
+    // Relative links do not start with a slash and are relative to the
52
+    // resource path.
53
+    string target = 9;
54
+
55
+    // Major specifies the major device number for character and block devices.
56
+    uint64 major = 10;
57
+
58
+    // Minor specifies the minor device number for character and block devices.
59
+    uint64 minor = 11;
60
+
61
+    // Xattr provides storage for extended attributes for the target resource.
62
+    repeated XAttr xattr = 12;
63
+
64
+    // Ads stores one or more alternate data streams for the target resource.
65
+    repeated ADSEntry ads = 13;
66
+
67
+}
68
+
69
+// XAttr encodes extended attributes for a resource.
70
+message XAttr {
71
+    // Name specifies the attribute name.
72
+    string name = 1;
73
+
74
+    // Data specifies the associated data for the attribute.
75
+    bytes data = 2;
76
+}
77
+
78
+// ADSEntry encodes information for a Windows Alternate Data Stream.
79
+message ADSEntry {
80
+    // Name specifices the stream name.
81
+    string name = 1;
82
+
83
+    // Data specifies the stream data.
84
+    // See also the description about the digest below.
85
+    bytes data = 2;
86
+
87
+    // Digest is a CAS representation of the stream data.
88
+    //
89
+    // At least one of data or digest MUST be specified, and either one of them
90
+    // SHOULD be specified.
91
+    //
92
+    // How to access the actual data using the digest is implementation-specific,
93
+    // and implementations can choose not to implement digest.
94
+    // So, digest SHOULD be used only when the stream data is large.
95
+    string digest = 3;
96
+}
0 97
new file mode 100644
... ...
@@ -0,0 +1,574 @@
0
+package continuity
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"os"
6
+	"reflect"
7
+	"sort"
8
+
9
+	pb "github.com/containerd/continuity/proto"
10
+	"github.com/opencontainers/go-digest"
11
+)
12
+
13
+// TODO(stevvooe): A record based model, somewhat sketched out at the bottom
14
+// of this file, will be more flexible. Another possibly is to tie the package
15
+// interface directly to the protobuf type. This will have efficiency
16
+// advantages at the cost coupling the nasty codegen types to the exported
17
+// interface.
18
+
19
+type Resource interface {
20
+	// Path provides the primary resource path relative to the bundle root. In
21
+	// cases where resources have more than one path, such as with hard links,
22
+	// this will return the primary path, which is often just the first entry.
23
+	Path() string
24
+
25
+	// Mode returns the
26
+	Mode() os.FileMode
27
+
28
+	UID() int64
29
+	GID() int64
30
+}
31
+
32
+// ByPath provides the canonical sort order for a set of resources. Use with
33
+// sort.Stable for deterministic sorting.
34
+type ByPath []Resource
35
+
36
+func (bp ByPath) Len() int           { return len(bp) }
37
+func (bp ByPath) Swap(i, j int)      { bp[i], bp[j] = bp[j], bp[i] }
38
+func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
39
+
40
+type XAttrer interface {
41
+	XAttrs() map[string][]byte
42
+}
43
+
44
+// Hardlinkable is an interface that a resource type satisfies if it can be a
45
+// hardlink target.
46
+type Hardlinkable interface {
47
+	// Paths returns all paths of the resource, including the primary path
48
+	// returned by Resource.Path. If len(Paths()) > 1, the resource is a hard
49
+	// link.
50
+	Paths() []string
51
+}
52
+
53
+type RegularFile interface {
54
+	Resource
55
+	XAttrer
56
+	Hardlinkable
57
+
58
+	Size() int64
59
+	Digests() []digest.Digest
60
+}
61
+
62
+// Merge two or more Resources into new file. Typically, this should be
63
+// used to merge regular files as hardlinks. If the files are not identical,
64
+// other than Paths and Digests, the merge will fail and an error will be
65
+// returned.
66
+func Merge(fs ...Resource) (Resource, error) {
67
+	if len(fs) < 1 {
68
+		return nil, fmt.Errorf("please provide a resource to merge")
69
+	}
70
+
71
+	if len(fs) == 1 {
72
+		return fs[0], nil
73
+	}
74
+
75
+	var paths []string
76
+	var digests []digest.Digest
77
+	bypath := map[string][]Resource{}
78
+
79
+	// The attributes are all compared against the first to make sure they
80
+	// agree before adding to the above collections. If any of these don't
81
+	// correctly validate, the merge fails.
82
+	prototype := fs[0]
83
+	xattrs := make(map[string][]byte)
84
+
85
+	// initialize xattrs for use below. All files must have same xattrs.
86
+	if prototypeXAttrer, ok := prototype.(XAttrer); ok {
87
+		for attr, value := range prototypeXAttrer.XAttrs() {
88
+			xattrs[attr] = value
89
+		}
90
+	}
91
+
92
+	for _, f := range fs {
93
+		h, isHardlinkable := f.(Hardlinkable)
94
+		if !isHardlinkable {
95
+			return nil, errNotAHardLink
96
+		}
97
+
98
+		if f.Mode() != prototype.Mode() {
99
+			return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
100
+		}
101
+
102
+		if f.UID() != prototype.UID() {
103
+			return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
104
+		}
105
+
106
+		if f.GID() != prototype.GID() {
107
+			return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
108
+		}
109
+
110
+		if xattrer, ok := f.(XAttrer); ok {
111
+			fxattrs := xattrer.XAttrs()
112
+			if !reflect.DeepEqual(fxattrs, xattrs) {
113
+				return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
114
+			}
115
+		}
116
+
117
+		for _, p := range h.Paths() {
118
+			pfs, ok := bypath[p]
119
+			if !ok {
120
+				// ensure paths are unique by only appending on a new path.
121
+				paths = append(paths, p)
122
+			}
123
+
124
+			bypath[p] = append(pfs, f)
125
+		}
126
+
127
+		if regFile, isRegFile := f.(RegularFile); isRegFile {
128
+			prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
129
+			if !prototypeIsRegFile {
130
+				return nil, errors.New("prototype is not a regular file")
131
+			}
132
+
133
+			if regFile.Size() != prototypeRegFile.Size() {
134
+				return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
135
+			}
136
+
137
+			digests = append(digests, regFile.Digests()...)
138
+		} else if device, isDevice := f.(Device); isDevice {
139
+			prototypeDevice, prototypeIsDevice := prototype.(Device)
140
+			if !prototypeIsDevice {
141
+				return nil, errors.New("prototype is not a device")
142
+			}
143
+
144
+			if device.Major() != prototypeDevice.Major() {
145
+				return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
146
+			}
147
+			if device.Minor() != prototypeDevice.Minor() {
148
+				return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
149
+			}
150
+		} else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
151
+			_, prototypeIsNamedPipe := prototype.(NamedPipe)
152
+			if !prototypeIsNamedPipe {
153
+				return nil, errors.New("prototype is not a named pipe")
154
+			}
155
+		} else {
156
+			return nil, errNotAHardLink
157
+		}
158
+	}
159
+
160
+	sort.Stable(sort.StringSlice(paths))
161
+
162
+	// Choose a "canonical" file. Really, it is just the first file to sort
163
+	// against. We also effectively select the very first digest as the
164
+	// "canonical" one for this file.
165
+	first := bypath[paths[0]][0]
166
+
167
+	resource := resource{
168
+		paths:  paths,
169
+		mode:   first.Mode(),
170
+		uid:    first.UID(),
171
+		gid:    first.GID(),
172
+		xattrs: xattrs,
173
+	}
174
+
175
+	switch typedF := first.(type) {
176
+	case RegularFile:
177
+		var err error
178
+		digests, err = uniqifyDigests(digests...)
179
+		if err != nil {
180
+			return nil, err
181
+		}
182
+
183
+		return &regularFile{
184
+			resource: resource,
185
+			size:     typedF.Size(),
186
+			digests:  digests,
187
+		}, nil
188
+	case Device:
189
+		return &device{
190
+			resource: resource,
191
+			major:    typedF.Major(),
192
+			minor:    typedF.Minor(),
193
+		}, nil
194
+
195
+	case NamedPipe:
196
+		return &namedPipe{
197
+			resource: resource,
198
+		}, nil
199
+
200
+	default:
201
+		return nil, errNotAHardLink
202
+	}
203
+}
204
+
205
+type Directory interface {
206
+	Resource
207
+	XAttrer
208
+
209
+	// Directory is a no-op method to identify directory objects by interface.
210
+	Directory()
211
+}
212
+
213
+type SymLink interface {
214
+	Resource
215
+
216
+	// Target returns the target of the symlink contained in the .
217
+	Target() string
218
+}
219
+
220
+type NamedPipe interface {
221
+	Resource
222
+	Hardlinkable
223
+	XAttrer
224
+
225
+	// Pipe is a no-op method to allow consistent resolution of NamedPipe
226
+	// interface.
227
+	Pipe()
228
+}
229
+
230
+type Device interface {
231
+	Resource
232
+	Hardlinkable
233
+	XAttrer
234
+
235
+	Major() uint64
236
+	Minor() uint64
237
+}
238
+
239
+type resource struct {
240
+	paths    []string
241
+	mode     os.FileMode
242
+	uid, gid int64
243
+	xattrs   map[string][]byte
244
+}
245
+
246
+var _ Resource = &resource{}
247
+
248
+func (r *resource) Path() string {
249
+	if len(r.paths) < 1 {
250
+		return ""
251
+	}
252
+
253
+	return r.paths[0]
254
+}
255
+
256
+func (r *resource) Mode() os.FileMode {
257
+	return r.mode
258
+}
259
+
260
+func (r *resource) UID() int64 {
261
+	return r.uid
262
+}
263
+
264
+func (r *resource) GID() int64 {
265
+	return r.gid
266
+}
267
+
268
+type regularFile struct {
269
+	resource
270
+	size    int64
271
+	digests []digest.Digest
272
+}
273
+
274
+var _ RegularFile = &regularFile{}
275
+
276
+// newRegularFile returns the RegularFile, using the populated base resource
277
+// and one or more digests of the content.
278
+func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
279
+	if !base.Mode().IsRegular() {
280
+		return nil, fmt.Errorf("not a regular file")
281
+	}
282
+
283
+	base.paths = make([]string, len(paths))
284
+	copy(base.paths, paths)
285
+
286
+	// make our own copy of digests
287
+	ds := make([]digest.Digest, len(dgsts))
288
+	copy(ds, dgsts)
289
+
290
+	return &regularFile{
291
+		resource: base,
292
+		size:     size,
293
+		digests:  ds,
294
+	}, nil
295
+}
296
+
297
+func (rf *regularFile) Paths() []string {
298
+	paths := make([]string, len(rf.paths))
299
+	copy(paths, rf.paths)
300
+	return paths
301
+}
302
+
303
+func (rf *regularFile) Size() int64 {
304
+	return rf.size
305
+}
306
+
307
+func (rf *regularFile) Digests() []digest.Digest {
308
+	digests := make([]digest.Digest, len(rf.digests))
309
+	copy(digests, rf.digests)
310
+	return digests
311
+}
312
+
313
+func (rf *regularFile) XAttrs() map[string][]byte {
314
+	xattrs := make(map[string][]byte, len(rf.xattrs))
315
+
316
+	for attr, value := range rf.xattrs {
317
+		xattrs[attr] = append(xattrs[attr], value...)
318
+	}
319
+
320
+	return xattrs
321
+}
322
+
323
+type directory struct {
324
+	resource
325
+}
326
+
327
+var _ Directory = &directory{}
328
+
329
+func newDirectory(base resource) (Directory, error) {
330
+	if !base.Mode().IsDir() {
331
+		return nil, fmt.Errorf("not a directory")
332
+	}
333
+
334
+	return &directory{
335
+		resource: base,
336
+	}, nil
337
+}
338
+
339
+func (d *directory) Directory() {}
340
+
341
+func (d *directory) XAttrs() map[string][]byte {
342
+	xattrs := make(map[string][]byte, len(d.xattrs))
343
+
344
+	for attr, value := range d.xattrs {
345
+		xattrs[attr] = append(xattrs[attr], value...)
346
+	}
347
+
348
+	return xattrs
349
+}
350
+
351
+type symLink struct {
352
+	resource
353
+	target string
354
+}
355
+
356
+var _ SymLink = &symLink{}
357
+
358
+func newSymLink(base resource, target string) (SymLink, error) {
359
+	if base.Mode()&os.ModeSymlink == 0 {
360
+		return nil, fmt.Errorf("not a symlink")
361
+	}
362
+
363
+	return &symLink{
364
+		resource: base,
365
+		target:   target,
366
+	}, nil
367
+}
368
+
369
+func (l *symLink) Target() string {
370
+	return l.target
371
+}
372
+
373
+type namedPipe struct {
374
+	resource
375
+}
376
+
377
+var _ NamedPipe = &namedPipe{}
378
+
379
+func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
380
+	if base.Mode()&os.ModeNamedPipe == 0 {
381
+		return nil, fmt.Errorf("not a namedpipe")
382
+	}
383
+
384
+	base.paths = make([]string, len(paths))
385
+	copy(base.paths, paths)
386
+
387
+	return &namedPipe{
388
+		resource: base,
389
+	}, nil
390
+}
391
+
392
+func (np *namedPipe) Pipe() {}
393
+
394
+func (np *namedPipe) Paths() []string {
395
+	paths := make([]string, len(np.paths))
396
+	copy(paths, np.paths)
397
+	return paths
398
+}
399
+
400
+func (np *namedPipe) XAttrs() map[string][]byte {
401
+	xattrs := make(map[string][]byte, len(np.xattrs))
402
+
403
+	for attr, value := range np.xattrs {
404
+		xattrs[attr] = append(xattrs[attr], value...)
405
+	}
406
+
407
+	return xattrs
408
+}
409
+
410
+type device struct {
411
+	resource
412
+	major, minor uint64
413
+}
414
+
415
+var _ Device = &device{}
416
+
417
+func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
418
+	if base.Mode()&os.ModeDevice == 0 {
419
+		return nil, fmt.Errorf("not a device")
420
+	}
421
+
422
+	base.paths = make([]string, len(paths))
423
+	copy(base.paths, paths)
424
+
425
+	return &device{
426
+		resource: base,
427
+		major:    major,
428
+		minor:    minor,
429
+	}, nil
430
+}
431
+
432
+func (d *device) Paths() []string {
433
+	paths := make([]string, len(d.paths))
434
+	copy(paths, d.paths)
435
+	return paths
436
+}
437
+
438
+func (d *device) XAttrs() map[string][]byte {
439
+	xattrs := make(map[string][]byte, len(d.xattrs))
440
+
441
+	for attr, value := range d.xattrs {
442
+		xattrs[attr] = append(xattrs[attr], value...)
443
+	}
444
+
445
+	return xattrs
446
+}
447
+
448
+func (d device) Major() uint64 {
449
+	return d.major
450
+}
451
+
452
+func (d device) Minor() uint64 {
453
+	return d.minor
454
+}
455
+
456
+// toProto converts a resource to a protobuf record. We'd like to push this
457
+// the individual types but we want to keep this all together during
458
+// prototyping.
459
+func toProto(resource Resource) *pb.Resource {
460
+	b := &pb.Resource{
461
+		Path: []string{resource.Path()},
462
+		Mode: uint32(resource.Mode()),
463
+		Uid:  resource.UID(),
464
+		Gid:  resource.GID(),
465
+	}
466
+
467
+	if xattrer, ok := resource.(XAttrer); ok {
468
+		// Sorts the XAttrs by name for consistent ordering.
469
+		keys := []string{}
470
+		xattrs := xattrer.XAttrs()
471
+		for k := range xattrs {
472
+			keys = append(keys, k)
473
+		}
474
+		sort.Strings(keys)
475
+
476
+		for _, k := range keys {
477
+			b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
478
+		}
479
+	}
480
+
481
+	switch r := resource.(type) {
482
+	case RegularFile:
483
+		b.Path = r.Paths()
484
+		b.Size = uint64(r.Size())
485
+
486
+		for _, dgst := range r.Digests() {
487
+			b.Digest = append(b.Digest, dgst.String())
488
+		}
489
+	case SymLink:
490
+		b.Target = r.Target()
491
+	case Device:
492
+		b.Major, b.Minor = r.Major(), r.Minor()
493
+		b.Path = r.Paths()
494
+	case NamedPipe:
495
+		b.Path = r.Paths()
496
+	}
497
+
498
+	// enforce a few stability guarantees that may not be provided by the
499
+	// resource implementation.
500
+	sort.Strings(b.Path)
501
+
502
+	return b
503
+}
504
+
505
+// fromProto converts from a protobuf Resource to a Resource interface.
506
+func fromProto(b *pb.Resource) (Resource, error) {
507
+	base := &resource{
508
+		paths: b.Path,
509
+		mode:  os.FileMode(b.Mode),
510
+		uid:   b.Uid,
511
+		gid:   b.Gid,
512
+	}
513
+
514
+	base.xattrs = make(map[string][]byte, len(b.Xattr))
515
+
516
+	for _, attr := range b.Xattr {
517
+		base.xattrs[attr.Name] = attr.Data
518
+	}
519
+
520
+	switch {
521
+	case base.Mode().IsRegular():
522
+		dgsts := make([]digest.Digest, len(b.Digest))
523
+		for i, dgst := range b.Digest {
524
+			// TODO(stevvooe): Should we be validating at this point?
525
+			dgsts[i] = digest.Digest(dgst)
526
+		}
527
+
528
+		return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
529
+	case base.Mode().IsDir():
530
+		return newDirectory(*base)
531
+	case base.Mode()&os.ModeSymlink != 0:
532
+		return newSymLink(*base, b.Target)
533
+	case base.Mode()&os.ModeNamedPipe != 0:
534
+		return newNamedPipe(*base, b.Path)
535
+	case base.Mode()&os.ModeDevice != 0:
536
+		return newDevice(*base, b.Path, b.Major, b.Minor)
537
+	}
538
+
539
+	return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
540
+}
541
+
542
+// NOTE(stevvooe): An alternative model that supports inline declaration.
543
+// Convenient for unit testing where inline declarations may be desirable but
544
+// creates an awkward API for the standard use case.
545
+
546
+// type ResourceKind int
547
+
548
+// const (
549
+// 	ResourceRegularFile = iota + 1
550
+// 	ResourceDirectory
551
+// 	ResourceSymLink
552
+// 	Resource
553
+// )
554
+
555
+// type Resource struct {
556
+// 	Kind         ResourceKind
557
+// 	Paths        []string
558
+// 	Mode         os.FileMode
559
+// 	UID          string
560
+// 	GID          string
561
+// 	Size         int64
562
+// 	Digests      []digest.Digest
563
+// 	Target       string
564
+// 	Major, Minor int
565
+// 	XAttrs       map[string][]byte
566
+// }
567
+
568
+// type RegularFile struct {
569
+// 	Paths   []string
570
+//  Size 	int64
571
+// 	Digests []digest.Digest
572
+// 	Perm    os.FileMode // os.ModePerm + sticky, setuid, setgid
573
+// }
0 574
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+// +build linux darwin freebsd solaris
1
+
2
+package continuity
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"syscall"
8
+)
9
+
10
+// newBaseResource returns a *resource, populated with data from p and fi,
11
+// where p will be populated directly.
12
+func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
13
+	// TODO(stevvooe): This need to be resolved for the container's root,
14
+	// where here we are really getting the host OS's value. We need to allow
15
+	// this be passed in and fixed up to make these uid/gid mappings portable.
16
+	// Either this can be part of the driver or we can achieve it through some
17
+	// other mechanism.
18
+	sys, ok := fi.Sys().(*syscall.Stat_t)
19
+	if !ok {
20
+		// TODO(stevvooe): This may not be a hard error for all platforms. We
21
+		// may want to move this to the driver.
22
+		return nil, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi)
23
+	}
24
+
25
+	return &resource{
26
+		paths: []string{p},
27
+		mode:  fi.Mode(),
28
+
29
+		uid: int64(sys.Uid),
30
+		gid: int64(sys.Gid),
31
+
32
+		// NOTE(stevvooe): Population of shared xattrs field is deferred to
33
+		// the resource types that populate it. Since they are a property of
34
+		// the context, they must set there.
35
+	}, nil
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package continuity
1
+
2
+import "os"
3
+
4
+// newBaseResource returns a *resource, populated with data from p and fi,
5
+// where p will be populated directly.
6
+func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
7
+	return &resource{
8
+		paths: []string{p},
9
+		mode:  fi.Mode(),
10
+	}, nil
11
+}
... ...
@@ -1,3 +1,5 @@
1
+// +build !windows
2
+
1 3
 /*
2 4
    Copyright The containerd Authors.
3 5
 
... ...
@@ -20,9 +20,6 @@ import (
20 20
 	"io"
21 21
 	"os"
22 22
 	"os/exec"
23
-
24
-	"github.com/pkg/errors"
25
-	"golang.org/x/sys/unix"
26 23
 )
27 24
 
28 25
 type IO interface {
... ...
@@ -37,49 +34,22 @@ type StartCloser interface {
37 37
 	CloseAfterStart() error
38 38
 }
39 39
 
40
-// NewPipeIO creates pipe pairs to be used with runc
41
-func NewPipeIO(uid, gid int) (i IO, err error) {
42
-	var pipes []*pipe
43
-	// cleanup in case of an error
44
-	defer func() {
45
-		if err != nil {
46
-			for _, p := range pipes {
47
-				p.Close()
48
-			}
49
-		}
50
-	}()
51
-	stdin, err := newPipe()
52
-	if err != nil {
53
-		return nil, err
54
-	}
55
-	pipes = append(pipes, stdin)
56
-	if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
57
-		return nil, errors.Wrap(err, "failed to chown stdin")
58
-	}
40
+// IOOpt sets I/O creation options
41
+type IOOpt func(*IOOption)
59 42
 
60
-	stdout, err := newPipe()
61
-	if err != nil {
62
-		return nil, err
63
-	}
64
-	pipes = append(pipes, stdout)
65
-	if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
66
-		return nil, errors.Wrap(err, "failed to chown stdout")
67
-	}
43
+// IOOption holds I/O creation options
44
+type IOOption struct {
45
+	OpenStdin  bool
46
+	OpenStdout bool
47
+	OpenStderr bool
48
+}
68 49
 
69
-	stderr, err := newPipe()
70
-	if err != nil {
71
-		return nil, err
50
+func defaultIOOption() *IOOption {
51
+	return &IOOption{
52
+		OpenStdin:  true,
53
+		OpenStdout: true,
54
+		OpenStderr: true,
72 55
 	}
73
-	pipes = append(pipes, stderr)
74
-	if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
75
-		return nil, errors.Wrap(err, "failed to chown stderr")
76
-	}
77
-
78
-	return &pipeIO{
79
-		in:  stdin,
80
-		out: stdout,
81
-		err: stderr,
82
-	}, nil
83 56
 }
84 57
 
85 58
 func newPipe() (*pipe, error) {
... ...
@@ -99,9 +69,9 @@ type pipe struct {
99 99
 }
100 100
 
101 101
 func (p *pipe) Close() error {
102
-	err := p.r.Close()
103
-	if werr := p.w.Close(); err == nil {
104
-		err = werr
102
+	err := p.w.Close()
103
+	if rerr := p.r.Close(); err == nil {
104
+		err = rerr
105 105
 	}
106 106
 	return err
107 107
 }
... ...
@@ -113,14 +83,23 @@ type pipeIO struct {
113 113
 }
114 114
 
115 115
 func (i *pipeIO) Stdin() io.WriteCloser {
116
+	if i.in == nil {
117
+		return nil
118
+	}
116 119
 	return i.in.w
117 120
 }
118 121
 
119 122
 func (i *pipeIO) Stdout() io.ReadCloser {
123
+	if i.out == nil {
124
+		return nil
125
+	}
120 126
 	return i.out.r
121 127
 }
122 128
 
123 129
 func (i *pipeIO) Stderr() io.ReadCloser {
130
+	if i.err == nil {
131
+		return nil
132
+	}
124 133
 	return i.err.r
125 134
 }
126 135
 
... ...
@@ -131,28 +110,38 @@ func (i *pipeIO) Close() error {
131 131
 		i.out,
132 132
 		i.err,
133 133
 	} {
134
-		if cerr := v.Close(); err == nil {
135
-			err = cerr
134
+		if v != nil {
135
+			if cerr := v.Close(); err == nil {
136
+				err = cerr
137
+			}
136 138
 		}
137 139
 	}
138 140
 	return err
139 141
 }
140 142
 
141 143
 func (i *pipeIO) CloseAfterStart() error {
142
-	for _, f := range []*os.File{
143
-		i.out.w,
144
-		i.err.w,
144
+	for _, f := range []*pipe{
145
+		i.out,
146
+		i.err,
145 147
 	} {
146
-		f.Close()
148
+		if f != nil {
149
+			f.w.Close()
150
+		}
147 151
 	}
148 152
 	return nil
149 153
 }
150 154
 
151 155
 // Set sets the io to the exec.Cmd
152 156
 func (i *pipeIO) Set(cmd *exec.Cmd) {
153
-	cmd.Stdin = i.in.r
154
-	cmd.Stdout = i.out.w
155
-	cmd.Stderr = i.err.w
157
+	if i.in != nil {
158
+		cmd.Stdin = i.in.r
159
+	}
160
+	if i.out != nil {
161
+		cmd.Stdout = i.out.w
162
+	}
163
+	if i.err != nil {
164
+		cmd.Stderr = i.err.w
165
+	}
156 166
 }
157 167
 
158 168
 func NewSTDIO() (IO, error) {
159 169
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+// +build !windows
1
+
2
+/*
3
+   Copyright The containerd Authors.
4
+
5
+   Licensed under the Apache License, Version 2.0 (the "License");
6
+   you may not use this file except in compliance with the License.
7
+   You may obtain a copy of the License at
8
+
9
+       http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+   Unless required by applicable law or agreed to in writing, software
12
+   distributed under the License is distributed on an "AS IS" BASIS,
13
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+   See the License for the specific language governing permissions and
15
+   limitations under the License.
16
+*/
17
+
18
+package runc
19
+
20
+import (
21
+	"github.com/pkg/errors"
22
+	"golang.org/x/sys/unix"
23
+)
24
+
25
+// NewPipeIO creates pipe pairs to be used with runc
26
+func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) {
27
+	option := defaultIOOption()
28
+	for _, o := range opts {
29
+		o(option)
30
+	}
31
+	var (
32
+		pipes                 []*pipe
33
+		stdin, stdout, stderr *pipe
34
+	)
35
+	// cleanup in case of an error
36
+	defer func() {
37
+		if err != nil {
38
+			for _, p := range pipes {
39
+				p.Close()
40
+			}
41
+		}
42
+	}()
43
+	if option.OpenStdin {
44
+		if stdin, err = newPipe(); err != nil {
45
+			return nil, err
46
+		}
47
+		pipes = append(pipes, stdin)
48
+		if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
49
+			return nil, errors.Wrap(err, "failed to chown stdin")
50
+		}
51
+	}
52
+	if option.OpenStdout {
53
+		if stdout, err = newPipe(); err != nil {
54
+			return nil, err
55
+		}
56
+		pipes = append(pipes, stdout)
57
+		if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
58
+			return nil, errors.Wrap(err, "failed to chown stdout")
59
+		}
60
+	}
61
+	if option.OpenStderr {
62
+		if stderr, err = newPipe(); err != nil {
63
+			return nil, err
64
+		}
65
+		pipes = append(pipes, stderr)
66
+		if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
67
+			return nil, errors.Wrap(err, "failed to chown stderr")
68
+		}
69
+	}
70
+	return &pipeIO{
71
+		in:  stdin,
72
+		out: stdout,
73
+		err: stderr,
74
+	}, nil
75
+}
0 76
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+// +build windows
1
+
2
+/*
3
+   Copyright The containerd Authors.
4
+
5
+   Licensed under the Apache License, Version 2.0 (the "License");
6
+   you may not use this file except in compliance with the License.
7
+   You may obtain a copy of the License at
8
+
9
+       http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+   Unless required by applicable law or agreed to in writing, software
12
+   distributed under the License is distributed on an "AS IS" BASIS,
13
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+   See the License for the specific language governing permissions and
15
+   limitations under the License.
16
+*/
17
+
18
+package runc
19
+
20
+// NewPipeIO creates pipe pairs to be used with runc
21
+func NewPipeIO(opts ...IOOpt) (i IO, err error) {
22
+	option := defaultIOOption()
23
+	for _, o := range opts {
24
+		o(option)
25
+	}
26
+	var (
27
+		pipes                 []*pipe
28
+		stdin, stdout, stderr *pipe
29
+	)
30
+	// cleanup in case of an error
31
+	defer func() {
32
+		if err != nil {
33
+			for _, p := range pipes {
34
+				p.Close()
35
+			}
36
+		}
37
+	}()
38
+	if option.OpenStdin {
39
+		if stdin, err = newPipe(); err != nil {
40
+			return nil, err
41
+		}
42
+		pipes = append(pipes, stdin)
43
+	}
44
+	if option.OpenStdout {
45
+		if stdout, err = newPipe(); err != nil {
46
+			return nil, err
47
+		}
48
+		pipes = append(pipes, stdout)
49
+	}
50
+	if option.OpenStderr {
51
+		if stderr, err = newPipe(); err != nil {
52
+			return nil, err
53
+		}
54
+		pipes = append(pipes, stderr)
55
+	}
56
+	return &pipeIO{
57
+		in:  stdin,
58
+		out: stdout,
59
+		err: stderr,
60
+	}, nil
61
+}
... ...
@@ -608,9 +608,8 @@ func parseVersion(data []byte) (Version, error) {
608 608
 	var v Version
609 609
 	parts := strings.Split(strings.TrimSpace(string(data)), "\n")
610 610
 	if len(parts) != 3 {
611
-		return v, ErrParseRuncVersion
611
+		return v, nil
612 612
 	}
613
-
614 613
 	for i, p := range []struct {
615 614
 		dest  *string
616 615
 		split string