Browse code

Windows: Experimental: Allow containerd for runtime

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

This is the first step in refactoring moby (dockerd) to use containerd on Windows.
Similar to the current model in Linux, this adds the option to enable it for runtime.
It does not switch the graphdriver to containerd snapshotters.

- Refactors libcontainerd to a series of subpackages so that either a
"local" containerd (1) or a "remote" (2) containerd can be loaded as opposed
to conditional compile as "local" for Windows and "remote" for Linux.

- Updates libcontainerd such that Windows has an option to allow the use of a
"remote" containerd. Here, it communicates over a named pipe using GRPC.
This is currently guarded behind the experimental flag, an environment variable,
and the providing of a pipename to connect to containerd.

- Infrastructure pieces such as under pkg/system to have helper functions for
determining whether containerd is being used.

(1) "local" containerd is what the daemon on Windows has used since inception.
It's not really containerd at all - it's simply local invocation of HCS APIs
directly in-process from the daemon through the Microsoft/hcsshim library.

(2) "remote" containerd is what docker on Linux uses for it's runtime. It means
that there is a separate containerd service running, and docker communicates over
GRPC to it.

To try this out, you will need to start with something like the following:

Window 1:
containerd --log-level debug

Window 2:
$env:DOCKER_WINDOWS_CONTAINERD=1
dockerd --experimental -D --containerd \\.\pipe\containerd-containerd

You will need the following binary from github.com/containerd/containerd in your path:
- containerd.exe

You will need the following binaries from github.com/Microsoft/hcsshim in your path:
- runhcs.exe
- containerd-shim-runhcs-v1.exe

For LCOW, it will require and initrd.img and kernel in `C:\Program Files\Linux Containers`.
This is no different to the current requirements. However, you may need updated binaries,
particularly initrd.img built from Microsoft/opengcs as (at the time of writing), Linuxkit
binaries are somewhat out of date.

Note that containerd and hcsshim for HCS v2 APIs do not yet support all the required
functionality needed for docker. This will come in time - this is a baby (although large)
step to migrating Docker on Windows to containerd.

Note that the HCS v2 APIs are only called on RS5+ builds. RS1..RS4 will still use
HCS v1 APIs as the v2 APIs were not fully developed enough on these builds to be usable.
This abstraction is done in HCSShim. (Referring specifically to runtime)

Note the LCOW graphdriver still uses HCS v1 APIs regardless.

Note also that this does not migrate docker to use containerd snapshotters
rather than graphdrivers. This needs to be done in conjunction with Linux also
doing the same switch.

John Howard authored on 2019/01/09 07:30:52
Showing 45 changed files
... ...
@@ -163,31 +163,9 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
163 163
 	}
164 164
 
165 165
 	ctx, cancel := context.WithCancel(context.Background())
166
-	if cli.Config.ContainerdAddr == "" && runtime.GOOS != "windows" {
167
-		systemContainerdAddr, ok, err := systemContainerdRunning(cli.Config.IsRootless())
168
-		if err != nil {
169
-			cancel()
170
-			return errors.Wrap(err, "could not determine whether the system containerd is running")
171
-		}
172
-		if !ok {
173
-			opts, err := cli.getContainerdDaemonOpts()
174
-			if err != nil {
175
-				cancel()
176
-				return errors.Wrap(err, "failed to generate containerd options")
177
-			}
178
-
179
-			r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...)
180
-			if err != nil {
181
-				cancel()
182
-				return errors.Wrap(err, "failed to start containerd")
183
-			}
184
-			cli.Config.ContainerdAddr = r.Address()
185
-
186
-			// Try to wait for containerd to shutdown
187
-			defer r.WaitTimeout(10 * time.Second)
188
-		} else {
189
-			cli.Config.ContainerdAddr = systemContainerdAddr
190
-		}
166
+	if err := cli.initContainerD(ctx); err != nil {
167
+		cancel()
168
+		return err
191 169
 	}
192 170
 	defer cancel()
193 171
 
... ...
@@ -3,12 +3,14 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	"context"
6 7
 	"fmt"
7 8
 	"net"
8 9
 	"os"
9 10
 	"os/signal"
10 11
 	"path/filepath"
11 12
 	"strconv"
13
+	"time"
12 14
 
13 15
 	"github.com/containerd/containerd/runtime/v1/linux"
14 16
 	"github.com/docker/docker/cmd/dockerd/hack"
... ...
@@ -18,6 +20,7 @@ import (
18 18
 	"github.com/docker/docker/pkg/homedir"
19 19
 	"github.com/docker/docker/rootless"
20 20
 	"github.com/docker/libnetwork/portallocator"
21
+	"github.com/pkg/errors"
21 22
 	"golang.org/x/sys/unix"
22 23
 )
23 24
 
... ...
@@ -145,3 +148,31 @@ func newCgroupParent(config *config.Config) string {
145 145
 	}
146 146
 	return cgroupParent
147 147
 }
148
+
149
+func (cli *DaemonCli) initContainerD(ctx context.Context) error {
150
+	if cli.Config.ContainerdAddr == "" {
151
+		systemContainerdAddr, ok, err := systemContainerdRunning(cli.Config.IsRootless())
152
+		if err != nil {
153
+			return errors.Wrap(err, "could not determine whether the system containerd is running")
154
+		}
155
+		if !ok {
156
+			opts, err := cli.getContainerdDaemonOpts()
157
+			if err != nil {
158
+				return errors.Wrap(err, "failed to generate containerd options")
159
+			}
160
+
161
+			r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...)
162
+			if err != nil {
163
+				return errors.Wrap(err, "failed to start containerd")
164
+			}
165
+			cli.Config.ContainerdAddr = r.Address()
166
+
167
+			// Try to wait for containerd to shutdown
168
+			defer r.WaitTimeout(10 * time.Second)
169
+		} else {
170
+			cli.Config.ContainerdAddr = systemContainerdAddr
171
+		}
172
+	}
173
+
174
+	return nil
175
+}
... ...
@@ -1,6 +1,7 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 	"net"
6 7
 	"os"
... ...
@@ -8,6 +9,7 @@ import (
8 8
 
9 9
 	"github.com/docker/docker/daemon/config"
10 10
 	"github.com/docker/docker/libcontainerd/supervisor"
11
+	"github.com/docker/docker/pkg/system"
11 12
 	"github.com/sirupsen/logrus"
12 13
 	"golang.org/x/sys/windows"
13 14
 )
... ...
@@ -90,3 +92,8 @@ func wrapListeners(proto string, ls []net.Listener) []net.Listener {
90 90
 func newCgroupParent(config *config.Config) string {
91 91
 	return ""
92 92
 }
93
+
94
+func (cli *DaemonCli) initContainerD(_ context.Context) error {
95
+	system.InitContainerdRuntime(cli.Config.Experimental, cli.Config.ContainerdAddr)
96
+	return nil
97
+}
... ...
@@ -42,6 +42,7 @@ import (
42 42
 	"github.com/moby/buildkit/util/resolver"
43 43
 	"github.com/moby/buildkit/util/tracing"
44 44
 	"github.com/sirupsen/logrus"
45
+
45 46
 	// register graph drivers
46 47
 	_ "github.com/docker/docker/daemon/graphdriver/register"
47 48
 	"github.com/docker/docker/daemon/stats"
... ...
@@ -50,6 +51,7 @@ import (
50 50
 	"github.com/docker/docker/image"
51 51
 	"github.com/docker/docker/layer"
52 52
 	"github.com/docker/docker/libcontainerd"
53
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
53 54
 	"github.com/docker/docker/pkg/idtools"
54 55
 	"github.com/docker/docker/pkg/locker"
55 56
 	"github.com/docker/docker/pkg/plugingetter"
... ...
@@ -105,7 +107,7 @@ type Daemon struct {
105 105
 	pluginManager         *plugin.Manager
106 106
 	linkIndex             *linkIndex
107 107
 	containerdCli         *containerd.Client
108
-	containerd            libcontainerd.Client
108
+	containerd            libcontainerdtypes.Client
109 109
 	defaultIsolation      containertypes.Isolation // Default isolation mode on Windows
110 110
 	clusterProvider       cluster.Provider
111 111
 	cluster               Cluster
... ...
@@ -351,11 +353,11 @@ func (daemon *Daemon) restore() error {
351 351
 						logrus.WithField("container", c.ID).WithField("state", s).
352 352
 							Info("restored container paused")
353 353
 						switch s {
354
-						case libcontainerd.StatusPaused, libcontainerd.StatusPausing:
354
+						case libcontainerdtypes.StatusPaused, libcontainerdtypes.StatusPausing:
355 355
 							// nothing to do
356
-						case libcontainerd.StatusStopped:
356
+						case libcontainerdtypes.StatusStopped:
357 357
 							alive = false
358
-						case libcontainerd.StatusUnknown:
358
+						case libcontainerdtypes.StatusUnknown:
359 359
 							logrus.WithField("container", c.ID).
360 360
 								Error("Unknown status for container during restore")
361 361
 						default:
... ...
@@ -502,6 +502,7 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig
502 502
 // conditionalMountOnStart is a platform specific helper function during the
503 503
 // container start to call mount.
504 504
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
505
+
505 506
 	// Bail out now for Linux containers. We cannot mount the containers filesystem on the
506 507
 	// host as it is a non-Windows filesystem.
507 508
 	if system.LCOWSupported() && container.OS != "windows" {
... ...
@@ -519,6 +520,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
519 519
 // conditionalUnmountOnCleanup is a platform specific helper function called
520 520
 // during the cleanup of a container to unmount.
521 521
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
522
+
522 523
 	// Bail out now for Linux containers
523 524
 	if system.LCOWSupported() && container.OS != "windows" {
524 525
 		return nil
... ...
@@ -9,7 +9,7 @@ import (
9 9
 
10 10
 	containerpkg "github.com/docker/docker/container"
11 11
 	"github.com/docker/docker/errdefs"
12
-	"github.com/docker/docker/libcontainerd"
12
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
13 13
 	"github.com/docker/docker/pkg/signal"
14 14
 	"github.com/pkg/errors"
15 15
 	"github.com/sirupsen/logrus"
... ...
@@ -177,5 +177,5 @@ func (daemon *Daemon) killPossiblyDeadProcess(container *containerpkg.Container,
177 177
 }
178 178
 
179 179
 func (daemon *Daemon) kill(c *containerpkg.Container, sig int) error {
180
-	return daemon.containerd.SignalProcess(context.Background(), c.ID, libcontainerd.InitProcessName, sig)
180
+	return daemon.containerd.SignalProcess(context.Background(), c.ID, libcontainerdtypes.InitProcessName, sig)
181 181
 }
... ...
@@ -10,7 +10,7 @@ import (
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12 12
 	"github.com/docker/docker/container"
13
-	"github.com/docker/docker/libcontainerd"
13
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
14 14
 	"github.com/docker/docker/restartmanager"
15 15
 	"github.com/sirupsen/logrus"
16 16
 )
... ...
@@ -27,14 +27,14 @@ func (daemon *Daemon) setStateCounter(c *container.Container) {
27 27
 }
28 28
 
29 29
 // ProcessEvent is called by libcontainerd whenever an event occurs
30
-func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libcontainerd.EventInfo) error {
30
+func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
31 31
 	c, err := daemon.GetContainer(id)
32 32
 	if c == nil || err != nil {
33 33
 		return fmt.Errorf("no such container: %s", id)
34 34
 	}
35 35
 
36 36
 	switch e {
37
-	case libcontainerd.EventOOM:
37
+	case libcontainerdtypes.EventOOM:
38 38
 		// StateOOM is Linux specific and should never be hit on Windows
39 39
 		if runtime.GOOS == "windows" {
40 40
 			return errors.New("received StateOOM from libcontainerd on Windows. This should never happen")
... ...
@@ -48,7 +48,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc
48 48
 		}
49 49
 
50 50
 		daemon.LogContainerEvent(c, "oom")
51
-	case libcontainerd.EventExit:
51
+	case libcontainerdtypes.EventExit:
52 52
 		if int(ei.Pid) == c.Pid {
53 53
 			c.Lock()
54 54
 			_, _, err := daemon.containerd.DeleteTask(context.Background(), c.ID)
... ...
@@ -140,7 +140,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc
140 140
 				"exec-pid":  ei.Pid,
141 141
 			}).Warn("Ignoring Exit Event, no such exec command found")
142 142
 		}
143
-	case libcontainerd.EventStart:
143
+	case libcontainerdtypes.EventStart:
144 144
 		c.Lock()
145 145
 		defer c.Unlock()
146 146
 
... ...
@@ -159,7 +159,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc
159 159
 			daemon.LogContainerEvent(c, "start")
160 160
 		}
161 161
 
162
-	case libcontainerd.EventPaused:
162
+	case libcontainerdtypes.EventPaused:
163 163
 		c.Lock()
164 164
 		defer c.Unlock()
165 165
 
... ...
@@ -172,7 +172,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc
172 172
 			}
173 173
 			daemon.LogContainerEvent(c, "pause")
174 174
 		}
175
-	case libcontainerd.EventResumed:
175
+	case libcontainerdtypes.EventResumed:
176 176
 		c.Lock()
177 177
 		defer c.Unlock()
178 178
 
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"fmt"
5 6
 	"io/ioutil"
6 7
 	"path/filepath"
... ...
@@ -15,7 +16,7 @@ import (
15 15
 	"github.com/docker/docker/pkg/system"
16 16
 	"github.com/opencontainers/runtime-spec/specs-go"
17 17
 	"github.com/pkg/errors"
18
-	"golang.org/x/sys/windows"
18
+	"github.com/sirupsen/logrus"
19 19
 	"golang.org/x/sys/windows/registry"
20 20
 )
21 21
 
... ...
@@ -25,6 +26,7 @@ const (
25 25
 )
26 26
 
27 27
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
28
+
28 29
 	img, err := daemon.imageService.GetImage(string(c.ImageID))
29 30
 	if err != nil {
30 31
 		return nil, err
... ...
@@ -219,11 +221,18 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
219 219
 		return nil, fmt.Errorf("Unsupported platform %q", img.OS)
220 220
 	}
221 221
 
222
+	if logrus.IsLevelEnabled(logrus.DebugLevel) {
223
+		if b, err := json.Marshal(&s); err == nil {
224
+			logrus.Debugf("Generated spec: %s", string(b))
225
+		}
226
+	}
227
+
222 228
 	return (*specs.Spec)(&s), nil
223 229
 }
224 230
 
225 231
 // Sets the Windows-specific fields of the OCI spec
226 232
 func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
233
+
227 234
 	if len(s.Process.Cwd) == 0 {
228 235
 		// We default to C:\ to workaround the oddity of the case that the
229 236
 		// default directory for cmd running as LocalSystem (or
... ...
@@ -396,29 +405,37 @@ func setResourcesInSpec(c *container.Container, s *specs.Spec, isHyperV bool) {
396 396
 			}
397 397
 		}
398 398
 	}
399
-	memoryLimit := uint64(c.HostConfig.Memory)
400
-	s.Windows.Resources = &specs.WindowsResources{
401
-		CPU: &specs.WindowsCPUResources{
399
+
400
+	if cpuMaximum != 0 || cpuShares != 0 || cpuCount != 0 {
401
+		if s.Windows.Resources == nil {
402
+			s.Windows.Resources = &specs.WindowsResources{}
403
+		}
404
+		s.Windows.Resources.CPU = &specs.WindowsCPUResources{
402 405
 			Maximum: &cpuMaximum,
403 406
 			Shares:  &cpuShares,
404 407
 			Count:   &cpuCount,
405
-		},
406
-		Memory: &specs.WindowsMemoryResources{
408
+		}
409
+	}
410
+
411
+	memoryLimit := uint64(c.HostConfig.Memory)
412
+	if memoryLimit != 0 {
413
+		if s.Windows.Resources == nil {
414
+			s.Windows.Resources = &specs.WindowsResources{}
415
+		}
416
+		s.Windows.Resources.Memory = &specs.WindowsMemoryResources{
407 417
 			Limit: &memoryLimit,
408
-		},
409
-		Storage: &specs.WindowsStorageResources{
410
-			Bps:  &c.HostConfig.IOMaximumBandwidth,
411
-			Iops: &c.HostConfig.IOMaximumIOps,
412
-		},
418
+		}
413 419
 	}
414
-}
415 420
 
416
-func escapeArgs(args []string) []string {
417
-	escapedArgs := make([]string, len(args))
418
-	for i, a := range args {
419
-		escapedArgs[i] = windows.EscapeArg(a)
421
+	if c.HostConfig.IOMaximumBandwidth != 0 || c.HostConfig.IOMaximumIOps != 0 {
422
+		if s.Windows.Resources == nil {
423
+			s.Windows.Resources = &specs.WindowsResources{}
424
+		}
425
+		s.Windows.Resources.Storage = &specs.WindowsStorageResources{
426
+			Bps:  &c.HostConfig.IOMaximumBandwidth,
427
+			Iops: &c.HostConfig.IOMaximumIOps,
428
+		}
420 429
 	}
421
-	return escapedArgs
422 430
 }
423 431
 
424 432
 // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"fmt"
6 6
 	"time"
7 7
 
8
-	"github.com/docker/docker/libcontainerd"
8
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
9 9
 )
10 10
 
11 11
 // ContainerResize changes the size of the TTY of the process running
... ...
@@ -20,7 +20,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
20 20
 		return errNotRunning(container.ID)
21 21
 	}
22 22
 
23
-	if err = daemon.containerd.ResizeTerminal(context.Background(), container.ID, libcontainerd.InitProcessName, width, height); err == nil {
23
+	if err = daemon.containerd.ResizeTerminal(context.Background(), container.ID, libcontainerdtypes.InitProcessName, width, height); err == nil {
24 24
 		attributes := map[string]string{
25 25
 			"height": fmt.Sprintf("%d", height),
26 26
 			"width":  fmt.Sprintf("%d", width),
... ...
@@ -1,11 +1,23 @@
1 1
 package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4
+	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
4 5
 	"github.com/Microsoft/opengcs/client"
5 6
 	"github.com/docker/docker/container"
7
+	"github.com/docker/docker/pkg/system"
6 8
 )
7 9
 
8 10
 func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) (interface{}, error) {
11
+
12
+	// Set the runtime options to debug regardless of current logging level.
13
+	if system.ContainerdRuntimeSupported() {
14
+		opts := &options.Options{Debug: true}
15
+		return opts, nil
16
+	}
17
+
18
+	// TODO @jhowardmsft (containerd) - Probably need to revisit LCOW options here
19
+	// rather than blindly ignoring them.
20
+
9 21
 	// LCOW options.
10 22
 	if container.OS == "linux" {
11 23
 		config := &client.Config{}
... ...
@@ -4,12 +4,12 @@ import (
4 4
 	"time"
5 5
 
6 6
 	"github.com/docker/docker/api/types/container"
7
-	"github.com/docker/docker/libcontainerd"
7
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
8 8
 	"github.com/opencontainers/runtime-spec/specs-go"
9 9
 )
10 10
 
11
-func toContainerdResources(resources container.Resources) *libcontainerd.Resources {
12
-	var r libcontainerd.Resources
11
+func toContainerdResources(resources container.Resources) *libcontainerdtypes.Resources {
12
+	var r libcontainerdtypes.Resources
13 13
 
14 14
 	r.BlockIO = &specs.LinuxBlockIO{
15 15
 		Weight: &resources.BlkioWeight,
... ...
@@ -2,10 +2,10 @@ package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4 4
 	"github.com/docker/docker/api/types/container"
5
-	"github.com/docker/docker/libcontainerd"
5
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
6 6
 )
7 7
 
8
-func toContainerdResources(resources container.Resources) *libcontainerd.Resources {
8
+func toContainerdResources(resources container.Resources) *libcontainerdtypes.Resources {
9 9
 	// We don't support update, so do nothing
10 10
 	return nil
11 11
 }
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"github.com/containerd/containerd"
10
-	"github.com/docker/docker/libcontainerd"
10
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
11 11
 	specs "github.com/opencontainers/runtime-spec/specs-go"
12 12
 )
13 13
 
... ...
@@ -18,19 +18,19 @@ type MockContainerdClient struct {
18 18
 func (c *MockContainerdClient) Version(ctx context.Context) (containerd.Version, error) {
19 19
 	return containerd.Version{}, nil
20 20
 }
21
-func (c *MockContainerdClient) Restore(ctx context.Context, containerID string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) {
21
+func (c *MockContainerdClient) Restore(ctx context.Context, containerID string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
22 22
 	return false, 0, nil
23 23
 }
24 24
 func (c *MockContainerdClient) Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error {
25 25
 	return nil
26 26
 }
27
-func (c *MockContainerdClient) Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) {
27
+func (c *MockContainerdClient) Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error) {
28 28
 	return 0, nil
29 29
 }
30 30
 func (c *MockContainerdClient) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
31 31
 	return nil
32 32
 }
33
-func (c *MockContainerdClient) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerd.StdioCallback) (int, error) {
33
+func (c *MockContainerdClient) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
34 34
 	return 0, nil
35 35
 }
36 36
 func (c *MockContainerdClient) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
... ...
@@ -41,23 +41,23 @@ func (c *MockContainerdClient) CloseStdin(ctx context.Context, containerID, proc
41 41
 }
42 42
 func (c *MockContainerdClient) Pause(ctx context.Context, containerID string) error  { return nil }
43 43
 func (c *MockContainerdClient) Resume(ctx context.Context, containerID string) error { return nil }
44
-func (c *MockContainerdClient) Stats(ctx context.Context, containerID string) (*libcontainerd.Stats, error) {
44
+func (c *MockContainerdClient) Stats(ctx context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
45 45
 	return nil, nil
46 46
 }
47 47
 func (c *MockContainerdClient) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
48 48
 	return nil, nil
49 49
 }
50
-func (c *MockContainerdClient) Summary(ctx context.Context, containerID string) ([]libcontainerd.Summary, error) {
50
+func (c *MockContainerdClient) Summary(ctx context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
51 51
 	return nil, nil
52 52
 }
53 53
 func (c *MockContainerdClient) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
54 54
 	return 0, time.Time{}, nil
55 55
 }
56 56
 func (c *MockContainerdClient) Delete(ctx context.Context, containerID string) error { return nil }
57
-func (c *MockContainerdClient) Status(ctx context.Context, containerID string) (libcontainerd.Status, error) {
57
+func (c *MockContainerdClient) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
58 58
 	return "null", nil
59 59
 }
60
-func (c *MockContainerdClient) UpdateResources(ctx context.Context, containerID string, resources *libcontainerd.Resources) error {
60
+func (c *MockContainerdClient) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
61 61
 	return nil
62 62
 }
63 63
 func (c *MockContainerdClient) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
64 64
deleted file mode 100644
... ...
@@ -1,918 +0,0 @@
1
-// +build !windows
2
-
3
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
4
-
5
-import (
6
-	"context"
7
-	"encoding/json"
8
-	"fmt"
9
-	"io"
10
-	"os"
11
-	"path/filepath"
12
-	"reflect"
13
-	"runtime"
14
-	"strings"
15
-	"sync"
16
-	"syscall"
17
-	"time"
18
-
19
-	"github.com/containerd/containerd"
20
-	apievents "github.com/containerd/containerd/api/events"
21
-	"github.com/containerd/containerd/api/types"
22
-	"github.com/containerd/containerd/archive"
23
-	"github.com/containerd/containerd/cio"
24
-	"github.com/containerd/containerd/content"
25
-	containerderrors "github.com/containerd/containerd/errdefs"
26
-	"github.com/containerd/containerd/events"
27
-	"github.com/containerd/containerd/images"
28
-	"github.com/containerd/containerd/runtime/linux/runctypes"
29
-	"github.com/containerd/typeurl"
30
-	"github.com/docker/docker/errdefs"
31
-	"github.com/docker/docker/pkg/ioutils"
32
-	v1 "github.com/opencontainers/image-spec/specs-go/v1"
33
-	specs "github.com/opencontainers/runtime-spec/specs-go"
34
-	"github.com/pkg/errors"
35
-	"github.com/sirupsen/logrus"
36
-	"google.golang.org/grpc/codes"
37
-	"google.golang.org/grpc/status"
38
-)
39
-
40
-// InitProcessName is the name given to the first process of a
41
-// container
42
-const InitProcessName = "init"
43
-
44
-type container struct {
45
-	mu sync.Mutex
46
-
47
-	bundleDir string
48
-	ctr       containerd.Container
49
-	task      containerd.Task
50
-	execs     map[string]containerd.Process
51
-	oomKilled bool
52
-}
53
-
54
-func (c *container) setTask(t containerd.Task) {
55
-	c.mu.Lock()
56
-	c.task = t
57
-	c.mu.Unlock()
58
-}
59
-
60
-func (c *container) getTask() containerd.Task {
61
-	c.mu.Lock()
62
-	t := c.task
63
-	c.mu.Unlock()
64
-	return t
65
-}
66
-
67
-func (c *container) addProcess(id string, p containerd.Process) {
68
-	c.mu.Lock()
69
-	if c.execs == nil {
70
-		c.execs = make(map[string]containerd.Process)
71
-	}
72
-	c.execs[id] = p
73
-	c.mu.Unlock()
74
-}
75
-
76
-func (c *container) deleteProcess(id string) {
77
-	c.mu.Lock()
78
-	delete(c.execs, id)
79
-	c.mu.Unlock()
80
-}
81
-
82
-func (c *container) getProcess(id string) containerd.Process {
83
-	c.mu.Lock()
84
-	p := c.execs[id]
85
-	c.mu.Unlock()
86
-	return p
87
-}
88
-
89
-func (c *container) setOOMKilled(killed bool) {
90
-	c.mu.Lock()
91
-	c.oomKilled = killed
92
-	c.mu.Unlock()
93
-}
94
-
95
-func (c *container) getOOMKilled() bool {
96
-	c.mu.Lock()
97
-	killed := c.oomKilled
98
-	c.mu.Unlock()
99
-	return killed
100
-}
101
-
102
-type client struct {
103
-	sync.RWMutex // protects containers map
104
-
105
-	client   *containerd.Client
106
-	stateDir string
107
-	logger   *logrus.Entry
108
-	ns       string
109
-
110
-	backend    Backend
111
-	eventQ     queue
112
-	containers map[string]*container
113
-}
114
-
115
-// NewClient creates a new libcontainerd client from a containerd client
116
-func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b Backend) (Client, error) {
117
-	c := &client{
118
-		client:     cli,
119
-		stateDir:   stateDir,
120
-		logger:     logrus.WithField("module", "libcontainerd").WithField("namespace", ns),
121
-		ns:         ns,
122
-		backend:    b,
123
-		containers: make(map[string]*container),
124
-	}
125
-
126
-	go c.processEventStream(ctx, ns)
127
-
128
-	return c, nil
129
-}
130
-
131
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
132
-	return c.client.Version(ctx)
133
-}
134
-
135
-// Restore loads the containerd container.
136
-// It should not be called concurrently with any other operation for the given ID.
137
-func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (alive bool, pid int, err error) {
138
-	c.Lock()
139
-	_, ok := c.containers[id]
140
-	if ok {
141
-		c.Unlock()
142
-		return false, 0, errors.WithStack(newConflictError("id already in use"))
143
-	}
144
-
145
-	cntr := &container{}
146
-	c.containers[id] = cntr
147
-	cntr.mu.Lock()
148
-	defer cntr.mu.Unlock()
149
-
150
-	c.Unlock()
151
-
152
-	defer func() {
153
-		if err != nil {
154
-			c.Lock()
155
-			delete(c.containers, id)
156
-			c.Unlock()
157
-		}
158
-	}()
159
-
160
-	var dio *cio.DirectIO
161
-	defer func() {
162
-		if err != nil && dio != nil {
163
-			dio.Cancel()
164
-			dio.Close()
165
-		}
166
-		err = wrapError(err)
167
-	}()
168
-
169
-	ctr, err := c.client.LoadContainer(ctx, id)
170
-	if err != nil {
171
-		return false, -1, errors.WithStack(wrapError(err))
172
-	}
173
-
174
-	attachIO := func(fifos *cio.FIFOSet) (cio.IO, error) {
175
-		// dio must be assigned to the previously defined dio for the defer above
176
-		// to handle cleanup
177
-		dio, err = cio.NewDirectIO(ctx, fifos)
178
-		if err != nil {
179
-			return nil, err
180
-		}
181
-		return attachStdio(dio)
182
-	}
183
-	t, err := ctr.Task(ctx, attachIO)
184
-	if err != nil && !containerderrors.IsNotFound(err) {
185
-		return false, -1, errors.Wrap(wrapError(err), "error getting containerd task for container")
186
-	}
187
-
188
-	if t != nil {
189
-		s, err := t.Status(ctx)
190
-		if err != nil {
191
-			return false, -1, errors.Wrap(wrapError(err), "error getting task status")
192
-		}
193
-
194
-		alive = s.Status != containerd.Stopped
195
-		pid = int(t.Pid())
196
-	}
197
-
198
-	cntr.bundleDir = filepath.Join(c.stateDir, id)
199
-	cntr.ctr = ctr
200
-	cntr.task = t
201
-	// TODO(mlaventure): load execs
202
-
203
-	c.logger.WithFields(logrus.Fields{
204
-		"container": id,
205
-		"alive":     alive,
206
-		"pid":       pid,
207
-	}).Debug("restored container")
208
-
209
-	return alive, pid, nil
210
-}
211
-
212
-func (c *client) Create(ctx context.Context, id string, ociSpec *specs.Spec, runtimeOptions interface{}) error {
213
-	if ctr := c.getContainer(id); ctr != nil {
214
-		return errors.WithStack(newConflictError("id already in use"))
215
-	}
216
-
217
-	bdir, err := prepareBundleDir(filepath.Join(c.stateDir, id), ociSpec)
218
-	if err != nil {
219
-		return errdefs.System(errors.Wrap(err, "prepare bundle dir failed"))
220
-	}
221
-
222
-	c.logger.WithField("bundle", bdir).WithField("root", ociSpec.Root.Path).Debug("bundle dir created")
223
-
224
-	cdCtr, err := c.client.NewContainer(ctx, id,
225
-		containerd.WithSpec(ociSpec),
226
-		// TODO(mlaventure): when containerd support lcow, revisit runtime value
227
-		containerd.WithRuntime(fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS), runtimeOptions))
228
-	if err != nil {
229
-		return wrapError(err)
230
-	}
231
-
232
-	c.Lock()
233
-	c.containers[id] = &container{
234
-		bundleDir: bdir,
235
-		ctr:       cdCtr,
236
-	}
237
-	c.Unlock()
238
-
239
-	return nil
240
-}
241
-
242
-// Start create and start a task for the specified containerd id
243
-func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio StdioCallback) (int, error) {
244
-	ctr := c.getContainer(id)
245
-	if ctr == nil {
246
-		return -1, errors.WithStack(newNotFoundError("no such container"))
247
-	}
248
-	if t := ctr.getTask(); t != nil {
249
-		return -1, errors.WithStack(newConflictError("container already started"))
250
-	}
251
-
252
-	var (
253
-		cp             *types.Descriptor
254
-		t              containerd.Task
255
-		rio            cio.IO
256
-		err            error
257
-		stdinCloseSync = make(chan struct{})
258
-	)
259
-
260
-	if checkpointDir != "" {
261
-		// write checkpoint to the content store
262
-		tar := archive.Diff(ctx, "", checkpointDir)
263
-		cp, err = c.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, checkpointDir, tar)
264
-		// remove the checkpoint when we're done
265
-		defer func() {
266
-			if cp != nil {
267
-				err := c.client.ContentStore().Delete(context.Background(), cp.Digest)
268
-				if err != nil {
269
-					c.logger.WithError(err).WithFields(logrus.Fields{
270
-						"ref":    checkpointDir,
271
-						"digest": cp.Digest,
272
-					}).Warnf("failed to delete temporary checkpoint entry")
273
-				}
274
-			}
275
-		}()
276
-		if err := tar.Close(); err != nil {
277
-			return -1, errors.Wrap(err, "failed to close checkpoint tar stream")
278
-		}
279
-		if err != nil {
280
-			return -1, errors.Wrapf(err, "failed to upload checkpoint to containerd")
281
-		}
282
-	}
283
-
284
-	spec, err := ctr.ctr.Spec(ctx)
285
-	if err != nil {
286
-		return -1, errors.Wrap(err, "failed to retrieve spec")
287
-	}
288
-	uid, gid := getSpecUser(spec)
289
-	t, err = ctr.ctr.NewTask(ctx,
290
-		func(id string) (cio.IO, error) {
291
-			fifos := newFIFOSet(ctr.bundleDir, InitProcessName, withStdin, spec.Process.Terminal)
292
-
293
-			rio, err = c.createIO(fifos, id, InitProcessName, stdinCloseSync, attachStdio)
294
-			return rio, err
295
-		},
296
-		func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
297
-			info.Checkpoint = cp
298
-			info.Options = &runctypes.CreateOptions{
299
-				IoUid:       uint32(uid),
300
-				IoGid:       uint32(gid),
301
-				NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
302
-			}
303
-			return nil
304
-		})
305
-	if err != nil {
306
-		close(stdinCloseSync)
307
-		if rio != nil {
308
-			rio.Cancel()
309
-			rio.Close()
310
-		}
311
-		return -1, wrapError(err)
312
-	}
313
-
314
-	ctr.setTask(t)
315
-
316
-	// Signal c.createIO that it can call CloseIO
317
-	close(stdinCloseSync)
318
-
319
-	if err := t.Start(ctx); err != nil {
320
-		if _, err := t.Delete(ctx); err != nil {
321
-			c.logger.WithError(err).WithField("container", id).
322
-				Error("failed to delete task after fail start")
323
-		}
324
-		ctr.setTask(nil)
325
-		return -1, wrapError(err)
326
-	}
327
-
328
-	return int(t.Pid()), nil
329
-}
330
-
331
-// Exec creates exec process.
332
-//
333
-// The containerd client calls Exec to register the exec config in the shim side.
334
-// When the client calls Start, the shim will create stdin fifo if needs. But
335
-// for the container main process, the stdin fifo will be created in Create not
336
-// the Start call. stdinCloseSync channel should be closed after Start exec
337
-// process.
338
-func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) {
339
-	ctr := c.getContainer(containerID)
340
-	if ctr == nil {
341
-		return -1, errors.WithStack(newNotFoundError("no such container"))
342
-	}
343
-	t := ctr.getTask()
344
-	if t == nil {
345
-		return -1, errors.WithStack(newInvalidParameterError("container is not running"))
346
-	}
347
-
348
-	if p := ctr.getProcess(processID); p != nil {
349
-		return -1, errors.WithStack(newConflictError("id already in use"))
350
-	}
351
-
352
-	var (
353
-		p              containerd.Process
354
-		rio            cio.IO
355
-		err            error
356
-		stdinCloseSync = make(chan struct{})
357
-	)
358
-
359
-	fifos := newFIFOSet(ctr.bundleDir, processID, withStdin, spec.Terminal)
360
-
361
-	defer func() {
362
-		if err != nil {
363
-			if rio != nil {
364
-				rio.Cancel()
365
-				rio.Close()
366
-			}
367
-		}
368
-	}()
369
-
370
-	p, err = t.Exec(ctx, processID, spec, func(id string) (cio.IO, error) {
371
-		rio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio)
372
-		return rio, err
373
-	})
374
-	if err != nil {
375
-		close(stdinCloseSync)
376
-		return -1, wrapError(err)
377
-	}
378
-
379
-	ctr.addProcess(processID, p)
380
-
381
-	// Signal c.createIO that it can call CloseIO
382
-	//
383
-	// the stdin of exec process will be created after p.Start in containerd
384
-	defer close(stdinCloseSync)
385
-
386
-	if err = p.Start(ctx); err != nil {
387
-		// use new context for cleanup because old one may be cancelled by user, but leave a timeout to make sure
388
-		// we are not waiting forever if containerd is unresponsive or to work around fifo cancelling issues in
389
-		// older containerd-shim
390
-		ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
391
-		defer cancel()
392
-		p.Delete(ctx)
393
-		ctr.deleteProcess(processID)
394
-		return -1, wrapError(err)
395
-	}
396
-
397
-	return int(p.Pid()), nil
398
-}
399
-
400
-func (c *client) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
401
-	p, err := c.getProcess(containerID, processID)
402
-	if err != nil {
403
-		return err
404
-	}
405
-	return wrapError(p.Kill(ctx, syscall.Signal(signal)))
406
-}
407
-
408
-func (c *client) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
409
-	p, err := c.getProcess(containerID, processID)
410
-	if err != nil {
411
-		return err
412
-	}
413
-
414
-	return p.Resize(ctx, uint32(width), uint32(height))
415
-}
416
-
417
-func (c *client) CloseStdin(ctx context.Context, containerID, processID string) error {
418
-	p, err := c.getProcess(containerID, processID)
419
-	if err != nil {
420
-		return err
421
-	}
422
-
423
-	return p.CloseIO(ctx, containerd.WithStdinCloser)
424
-}
425
-
426
-func (c *client) Pause(ctx context.Context, containerID string) error {
427
-	p, err := c.getProcess(containerID, InitProcessName)
428
-	if err != nil {
429
-		return err
430
-	}
431
-
432
-	return wrapError(p.(containerd.Task).Pause(ctx))
433
-}
434
-
435
-func (c *client) Resume(ctx context.Context, containerID string) error {
436
-	p, err := c.getProcess(containerID, InitProcessName)
437
-	if err != nil {
438
-		return err
439
-	}
440
-
441
-	return p.(containerd.Task).Resume(ctx)
442
-}
443
-
444
-func (c *client) Stats(ctx context.Context, containerID string) (*Stats, error) {
445
-	p, err := c.getProcess(containerID, InitProcessName)
446
-	if err != nil {
447
-		return nil, err
448
-	}
449
-
450
-	m, err := p.(containerd.Task).Metrics(ctx)
451
-	if err != nil {
452
-		return nil, err
453
-	}
454
-
455
-	v, err := typeurl.UnmarshalAny(m.Data)
456
-	if err != nil {
457
-		return nil, err
458
-	}
459
-	return interfaceToStats(m.Timestamp, v), nil
460
-}
461
-
462
-func (c *client) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
463
-	p, err := c.getProcess(containerID, InitProcessName)
464
-	if err != nil {
465
-		return nil, err
466
-	}
467
-
468
-	pis, err := p.(containerd.Task).Pids(ctx)
469
-	if err != nil {
470
-		return nil, err
471
-	}
472
-
473
-	var pids []uint32
474
-	for _, i := range pis {
475
-		pids = append(pids, i.Pid)
476
-	}
477
-
478
-	return pids, nil
479
-}
480
-
481
-func (c *client) Summary(ctx context.Context, containerID string) ([]Summary, error) {
482
-	p, err := c.getProcess(containerID, InitProcessName)
483
-	if err != nil {
484
-		return nil, err
485
-	}
486
-
487
-	pis, err := p.(containerd.Task).Pids(ctx)
488
-	if err != nil {
489
-		return nil, err
490
-	}
491
-
492
-	var infos []Summary
493
-	for _, pi := range pis {
494
-		i, err := typeurl.UnmarshalAny(pi.Info)
495
-		if err != nil {
496
-			return nil, errors.Wrap(err, "unable to decode process details")
497
-		}
498
-		s, err := summaryFromInterface(i)
499
-		if err != nil {
500
-			return nil, err
501
-		}
502
-		infos = append(infos, *s)
503
-	}
504
-
505
-	return infos, nil
506
-}
507
-
508
-func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
509
-	p, err := c.getProcess(containerID, InitProcessName)
510
-	if err != nil {
511
-		return 255, time.Now(), nil
512
-	}
513
-
514
-	status, err := p.(containerd.Task).Delete(ctx)
515
-	if err != nil {
516
-		return 255, time.Now(), nil
517
-	}
518
-
519
-	if ctr := c.getContainer(containerID); ctr != nil {
520
-		ctr.setTask(nil)
521
-	}
522
-	return status.ExitCode(), status.ExitTime(), nil
523
-}
524
-
525
-func (c *client) Delete(ctx context.Context, containerID string) error {
526
-	ctr := c.getContainer(containerID)
527
-	if ctr == nil {
528
-		return errors.WithStack(newNotFoundError("no such container"))
529
-	}
530
-
531
-	if err := ctr.ctr.Delete(ctx); err != nil {
532
-		return wrapError(err)
533
-	}
534
-
535
-	if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" {
536
-		if err := os.RemoveAll(ctr.bundleDir); err != nil {
537
-			c.logger.WithError(err).WithFields(logrus.Fields{
538
-				"container": containerID,
539
-				"bundle":    ctr.bundleDir,
540
-			}).Error("failed to remove state dir")
541
-		}
542
-	}
543
-
544
-	c.removeContainer(containerID)
545
-
546
-	return nil
547
-}
548
-
549
-func (c *client) Status(ctx context.Context, containerID string) (Status, error) {
550
-	ctr := c.getContainer(containerID)
551
-	if ctr == nil {
552
-		return StatusUnknown, errors.WithStack(newNotFoundError("no such container"))
553
-	}
554
-
555
-	t := ctr.getTask()
556
-	if t == nil {
557
-		return StatusUnknown, errors.WithStack(newNotFoundError("no such task"))
558
-	}
559
-
560
-	s, err := t.Status(ctx)
561
-	if err != nil {
562
-		return StatusUnknown, wrapError(err)
563
-	}
564
-
565
-	return Status(s.Status), nil
566
-}
567
-
568
-func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
569
-	p, err := c.getProcess(containerID, InitProcessName)
570
-	if err != nil {
571
-		return err
572
-	}
573
-
574
-	opts := []containerd.CheckpointTaskOpts{}
575
-	if exit {
576
-		opts = append(opts, func(r *containerd.CheckpointTaskInfo) error {
577
-			if r.Options == nil {
578
-				r.Options = &runctypes.CheckpointOptions{
579
-					Exit: true,
580
-				}
581
-			} else {
582
-				opts, _ := r.Options.(*runctypes.CheckpointOptions)
583
-				opts.Exit = true
584
-			}
585
-			return nil
586
-		})
587
-	}
588
-	img, err := p.(containerd.Task).Checkpoint(ctx, opts...)
589
-	if err != nil {
590
-		return wrapError(err)
591
-	}
592
-	// Whatever happens, delete the checkpoint from containerd
593
-	defer func() {
594
-		err := c.client.ImageService().Delete(context.Background(), img.Name())
595
-		if err != nil {
596
-			c.logger.WithError(err).WithField("digest", img.Target().Digest).
597
-				Warnf("failed to delete checkpoint image")
598
-		}
599
-	}()
600
-
601
-	b, err := content.ReadBlob(ctx, c.client.ContentStore(), img.Target())
602
-	if err != nil {
603
-		return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data"))
604
-	}
605
-	var index v1.Index
606
-	if err := json.Unmarshal(b, &index); err != nil {
607
-		return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data"))
608
-	}
609
-
610
-	var cpDesc *v1.Descriptor
611
-	for _, m := range index.Manifests {
612
-		if m.MediaType == images.MediaTypeContainerd1Checkpoint {
613
-			cpDesc = &m
614
-			break
615
-		}
616
-	}
617
-	if cpDesc == nil {
618
-		return errdefs.System(errors.Wrapf(err, "invalid checkpoint"))
619
-	}
620
-
621
-	rat, err := c.client.ContentStore().ReaderAt(ctx, *cpDesc)
622
-	if err != nil {
623
-		return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader"))
624
-	}
625
-	defer rat.Close()
626
-	_, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat))
627
-	if err != nil {
628
-		return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader"))
629
-	}
630
-
631
-	return err
632
-}
633
-
634
-func (c *client) getContainer(id string) *container {
635
-	c.RLock()
636
-	ctr := c.containers[id]
637
-	c.RUnlock()
638
-
639
-	return ctr
640
-}
641
-
642
-func (c *client) removeContainer(id string) {
643
-	c.Lock()
644
-	delete(c.containers, id)
645
-	c.Unlock()
646
-}
647
-
648
-func (c *client) getProcess(containerID, processID string) (containerd.Process, error) {
649
-	ctr := c.getContainer(containerID)
650
-	if ctr == nil {
651
-		return nil, errors.WithStack(newNotFoundError("no such container"))
652
-	}
653
-
654
-	t := ctr.getTask()
655
-	if t == nil {
656
-		return nil, errors.WithStack(newNotFoundError("container is not running"))
657
-	}
658
-	if processID == InitProcessName {
659
-		return t, nil
660
-	}
661
-
662
-	p := ctr.getProcess(processID)
663
-	if p == nil {
664
-		return nil, errors.WithStack(newNotFoundError("no such exec"))
665
-	}
666
-	return p, nil
667
-}
668
-
669
-// createIO creates the io to be used by a process
670
-// This needs to get a pointer to interface as upon closure the process may not have yet been registered
671
-func (c *client) createIO(fifos *cio.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio StdioCallback) (cio.IO, error) {
672
-	var (
673
-		io  *cio.DirectIO
674
-		err error
675
-	)
676
-
677
-	io, err = cio.NewDirectIO(context.Background(), fifos)
678
-	if err != nil {
679
-		return nil, err
680
-	}
681
-
682
-	if io.Stdin != nil {
683
-		var (
684
-			err       error
685
-			stdinOnce sync.Once
686
-		)
687
-		pipe := io.Stdin
688
-		io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error {
689
-			stdinOnce.Do(func() {
690
-				err = pipe.Close()
691
-				// Do the rest in a new routine to avoid a deadlock if the
692
-				// Exec/Start call failed.
693
-				go func() {
694
-					<-stdinCloseSync
695
-					p, err := c.getProcess(containerID, processID)
696
-					if err == nil {
697
-						err = p.CloseIO(context.Background(), containerd.WithStdinCloser)
698
-						if err != nil && strings.Contains(err.Error(), "transport is closing") {
699
-							err = nil
700
-						}
701
-					}
702
-				}()
703
-			})
704
-			return err
705
-		})
706
-	}
707
-
708
-	rio, err := attachStdio(io)
709
-	if err != nil {
710
-		io.Cancel()
711
-		io.Close()
712
-	}
713
-	return rio, err
714
-}
715
-
716
-func (c *client) processEvent(ctr *container, et EventType, ei EventInfo) {
717
-	c.eventQ.append(ei.ContainerID, func() {
718
-		err := c.backend.ProcessEvent(ei.ContainerID, et, ei)
719
-		if err != nil {
720
-			c.logger.WithError(err).WithFields(logrus.Fields{
721
-				"container":  ei.ContainerID,
722
-				"event":      et,
723
-				"event-info": ei,
724
-			}).Error("failed to process event")
725
-		}
726
-
727
-		if et == EventExit && ei.ProcessID != ei.ContainerID {
728
-			p := ctr.getProcess(ei.ProcessID)
729
-			if p == nil {
730
-				c.logger.WithError(errors.New("no such process")).
731
-					WithFields(logrus.Fields{
732
-						"container": ei.ContainerID,
733
-						"process":   ei.ProcessID,
734
-					}).Error("exit event")
735
-				return
736
-			}
737
-			_, err = p.Delete(context.Background())
738
-			if err != nil {
739
-				c.logger.WithError(err).WithFields(logrus.Fields{
740
-					"container": ei.ContainerID,
741
-					"process":   ei.ProcessID,
742
-				}).Warn("failed to delete process")
743
-			}
744
-			ctr.deleteProcess(ei.ProcessID)
745
-
746
-			ctr := c.getContainer(ei.ContainerID)
747
-			if ctr == nil {
748
-				c.logger.WithFields(logrus.Fields{
749
-					"container": ei.ContainerID,
750
-				}).Error("failed to find container")
751
-			} else {
752
-				newFIFOSet(ctr.bundleDir, ei.ProcessID, true, false).Close()
753
-			}
754
-		}
755
-	})
756
-}
757
-
758
-func (c *client) processEventStream(ctx context.Context, ns string) {
759
-	var (
760
-		err error
761
-		ev  *events.Envelope
762
-		et  EventType
763
-		ei  EventInfo
764
-		ctr *container
765
-	)
766
-
767
-	// Filter on both namespace *and* topic. To create an "and" filter,
768
-	// this must be a single, comma-separated string
769
-	eventStream, errC := c.client.EventService().Subscribe(ctx, "namespace=="+ns+",topic~=|^/tasks/|")
770
-
771
-	c.logger.Debug("processing event stream")
772
-
773
-	var oomKilled bool
774
-	for {
775
-		select {
776
-		case err = <-errC:
777
-			if err != nil {
778
-				errStatus, ok := status.FromError(err)
779
-				if !ok || errStatus.Code() != codes.Canceled {
780
-					c.logger.WithError(err).Error("failed to get event")
781
-					go c.processEventStream(ctx, ns)
782
-				} else {
783
-					c.logger.WithError(ctx.Err()).Info("stopping event stream following graceful shutdown")
784
-				}
785
-			}
786
-			return
787
-		case ev = <-eventStream:
788
-			if ev.Event == nil {
789
-				c.logger.WithField("event", ev).Warn("invalid event")
790
-				continue
791
-			}
792
-
793
-			v, err := typeurl.UnmarshalAny(ev.Event)
794
-			if err != nil {
795
-				c.logger.WithError(err).WithField("event", ev).Warn("failed to unmarshal event")
796
-				continue
797
-			}
798
-
799
-			c.logger.WithField("topic", ev.Topic).Debug("event")
800
-
801
-			switch t := v.(type) {
802
-			case *apievents.TaskCreate:
803
-				et = EventCreate
804
-				ei = EventInfo{
805
-					ContainerID: t.ContainerID,
806
-					ProcessID:   t.ContainerID,
807
-					Pid:         t.Pid,
808
-				}
809
-			case *apievents.TaskStart:
810
-				et = EventStart
811
-				ei = EventInfo{
812
-					ContainerID: t.ContainerID,
813
-					ProcessID:   t.ContainerID,
814
-					Pid:         t.Pid,
815
-				}
816
-			case *apievents.TaskExit:
817
-				et = EventExit
818
-				ei = EventInfo{
819
-					ContainerID: t.ContainerID,
820
-					ProcessID:   t.ID,
821
-					Pid:         t.Pid,
822
-					ExitCode:    t.ExitStatus,
823
-					ExitedAt:    t.ExitedAt,
824
-				}
825
-			case *apievents.TaskOOM:
826
-				et = EventOOM
827
-				ei = EventInfo{
828
-					ContainerID: t.ContainerID,
829
-					OOMKilled:   true,
830
-				}
831
-				oomKilled = true
832
-			case *apievents.TaskExecAdded:
833
-				et = EventExecAdded
834
-				ei = EventInfo{
835
-					ContainerID: t.ContainerID,
836
-					ProcessID:   t.ExecID,
837
-				}
838
-			case *apievents.TaskExecStarted:
839
-				et = EventExecStarted
840
-				ei = EventInfo{
841
-					ContainerID: t.ContainerID,
842
-					ProcessID:   t.ExecID,
843
-					Pid:         t.Pid,
844
-				}
845
-			case *apievents.TaskPaused:
846
-				et = EventPaused
847
-				ei = EventInfo{
848
-					ContainerID: t.ContainerID,
849
-				}
850
-			case *apievents.TaskResumed:
851
-				et = EventResumed
852
-				ei = EventInfo{
853
-					ContainerID: t.ContainerID,
854
-				}
855
-			default:
856
-				c.logger.WithFields(logrus.Fields{
857
-					"topic": ev.Topic,
858
-					"type":  reflect.TypeOf(t)},
859
-				).Info("ignoring event")
860
-				continue
861
-			}
862
-
863
-			ctr = c.getContainer(ei.ContainerID)
864
-			if ctr == nil {
865
-				c.logger.WithField("container", ei.ContainerID).Warn("unknown container")
866
-				continue
867
-			}
868
-
869
-			if oomKilled {
870
-				ctr.setOOMKilled(true)
871
-				oomKilled = false
872
-			}
873
-			ei.OOMKilled = ctr.getOOMKilled()
874
-
875
-			c.processEvent(ctr, et, ei)
876
-		}
877
-	}
878
-}
879
-
880
-func (c *client) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
881
-	writer, err := c.client.ContentStore().Writer(ctx, content.WithRef(ref))
882
-	if err != nil {
883
-		return nil, err
884
-	}
885
-	defer writer.Close()
886
-	size, err := io.Copy(writer, r)
887
-	if err != nil {
888
-		return nil, err
889
-	}
890
-	labels := map[string]string{
891
-		"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
892
-	}
893
-	if err := writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
894
-		return nil, err
895
-	}
896
-	return &types.Descriptor{
897
-		MediaType: mediaType,
898
-		Digest:    writer.Digest(),
899
-		Size_:     size,
900
-	}, nil
901
-}
902
-
903
-func wrapError(err error) error {
904
-	switch {
905
-	case err == nil:
906
-		return nil
907
-	case containerderrors.IsNotFound(err):
908
-		return errdefs.NotFound(err)
909
-	}
910
-
911
-	msg := err.Error()
912
-	for _, s := range []string{"container does not exist", "not found", "no such container"} {
913
-		if strings.Contains(msg, s) {
914
-			return errdefs.NotFound(err)
915
-		}
916
-	}
917
-	return err
918
-}
919 1
deleted file mode 100644
... ...
@@ -1,108 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"context"
5
-	"fmt"
6
-	"os"
7
-	"path/filepath"
8
-	"strings"
9
-
10
-	"github.com/containerd/containerd"
11
-	"github.com/containerd/containerd/cio"
12
-	"github.com/docker/docker/pkg/idtools"
13
-	"github.com/opencontainers/runtime-spec/specs-go"
14
-	"github.com/sirupsen/logrus"
15
-)
16
-
17
-func summaryFromInterface(i interface{}) (*Summary, error) {
18
-	return &Summary{}, nil
19
-}
20
-
21
-func (c *client) UpdateResources(ctx context.Context, containerID string, resources *Resources) error {
22
-	p, err := c.getProcess(containerID, InitProcessName)
23
-	if err != nil {
24
-		return err
25
-	}
26
-
27
-	// go doesn't like the alias in 1.8, this means this need to be
28
-	// platform specific
29
-	return p.(containerd.Task).Update(ctx, containerd.WithResources((*specs.LinuxResources)(resources)))
30
-}
31
-
32
-func hostIDFromMap(id uint32, mp []specs.LinuxIDMapping) int {
33
-	for _, m := range mp {
34
-		if id >= m.ContainerID && id <= m.ContainerID+m.Size-1 {
35
-			return int(m.HostID + id - m.ContainerID)
36
-		}
37
-	}
38
-	return 0
39
-}
40
-
41
-func getSpecUser(ociSpec *specs.Spec) (int, int) {
42
-	var (
43
-		uid int
44
-		gid int
45
-	)
46
-
47
-	for _, ns := range ociSpec.Linux.Namespaces {
48
-		if ns.Type == specs.UserNamespace {
49
-			uid = hostIDFromMap(0, ociSpec.Linux.UIDMappings)
50
-			gid = hostIDFromMap(0, ociSpec.Linux.GIDMappings)
51
-			break
52
-		}
53
-	}
54
-
55
-	return uid, gid
56
-}
57
-
58
-func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
59
-	uid, gid := getSpecUser(ociSpec)
60
-	if uid == 0 && gid == 0 {
61
-		return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.Identity{UID: 0, GID: 0})
62
-	}
63
-
64
-	p := string(filepath.Separator)
65
-	components := strings.Split(bundleDir, string(filepath.Separator))
66
-	for _, d := range components[1:] {
67
-		p = filepath.Join(p, d)
68
-		fi, err := os.Stat(p)
69
-		if err != nil && !os.IsNotExist(err) {
70
-			return "", err
71
-		}
72
-		if os.IsNotExist(err) || fi.Mode()&1 == 0 {
73
-			p = fmt.Sprintf("%s.%d.%d", p, uid, gid)
74
-			if err := idtools.MkdirAndChown(p, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) {
75
-				return "", err
76
-			}
77
-		}
78
-	}
79
-
80
-	return p, nil
81
-}
82
-
83
-func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
84
-	config := cio.Config{
85
-		Terminal: withTerminal,
86
-		Stdout:   filepath.Join(bundleDir, processID+"-stdout"),
87
-	}
88
-	paths := []string{config.Stdout}
89
-
90
-	if withStdin {
91
-		config.Stdin = filepath.Join(bundleDir, processID+"-stdin")
92
-		paths = append(paths, config.Stdin)
93
-	}
94
-	if !withTerminal {
95
-		config.Stderr = filepath.Join(bundleDir, processID+"-stderr")
96
-		paths = append(paths, config.Stderr)
97
-	}
98
-	closer := func() error {
99
-		for _, path := range paths {
100
-			if err := os.RemoveAll(path); err != nil {
101
-				logrus.Warnf("libcontainerd: failed to remove fifo %v: %v", path, err)
102
-			}
103
-		}
104
-		return nil
105
-	}
106
-
107
-	return cio.NewFIFOSet(config, closer)
108
-}
109 1
deleted file mode 100644
... ...
@@ -1,55 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"fmt"
5
-	"path/filepath"
6
-
7
-	"github.com/containerd/containerd/cio"
8
-	"github.com/containerd/containerd/windows/hcsshimtypes"
9
-	specs "github.com/opencontainers/runtime-spec/specs-go"
10
-	"github.com/pkg/errors"
11
-)
12
-
13
-func summaryFromInterface(i interface{}) (*Summary, error) {
14
-	switch pd := i.(type) {
15
-	case *hcsshimtypes.ProcessDetails:
16
-		return &Summary{
17
-			CreateTimestamp:              pd.CreatedAt,
18
-			ImageName:                    pd.ImageName,
19
-			KernelTime100ns:              pd.KernelTime_100Ns,
20
-			MemoryCommitBytes:            pd.MemoryCommitBytes,
21
-			MemoryWorkingSetPrivateBytes: pd.MemoryWorkingSetPrivateBytes,
22
-			MemoryWorkingSetSharedBytes:  pd.MemoryWorkingSetSharedBytes,
23
-			ProcessId:                    pd.ProcessID,
24
-			UserTime100ns:                pd.UserTime_100Ns,
25
-		}, nil
26
-	default:
27
-		return nil, errors.Errorf("Unknown process details type %T", pd)
28
-	}
29
-}
30
-
31
-func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
32
-	return bundleDir, nil
33
-}
34
-
35
-func pipeName(containerID, processID, name string) string {
36
-	return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name)
37
-}
38
-
39
-func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
40
-	containerID := filepath.Base(bundleDir)
41
-	config := cio.Config{
42
-		Terminal: withTerminal,
43
-		Stdout:   pipeName(containerID, processID, "stdout"),
44
-	}
45
-
46
-	if withStdin {
47
-		config.Stdin = pipeName(containerID, processID, "stdin")
48
-	}
49
-
50
-	if !config.Terminal {
51
-		config.Stderr = pipeName(containerID, processID, "stderr")
52
-	}
53
-
54
-	return cio.NewFIFOSet(config, nil)
55
-}
56 1
deleted file mode 100644
... ...
@@ -1,1376 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"context"
5
-	"encoding/json"
6
-	"fmt"
7
-	"io/ioutil"
8
-	"os"
9
-	"path"
10
-	"path/filepath"
11
-	"regexp"
12
-	"strings"
13
-	"sync"
14
-	"syscall"
15
-	"time"
16
-
17
-	"github.com/Microsoft/hcsshim"
18
-	opengcs "github.com/Microsoft/opengcs/client"
19
-	"github.com/containerd/containerd"
20
-	"github.com/containerd/containerd/cio"
21
-	"github.com/docker/docker/pkg/sysinfo"
22
-	"github.com/docker/docker/pkg/system"
23
-	specs "github.com/opencontainers/runtime-spec/specs-go"
24
-	"github.com/pkg/errors"
25
-	"github.com/sirupsen/logrus"
26
-	"golang.org/x/sys/windows"
27
-)
28
-
29
-const InitProcessName = "init"
30
-
31
-type process struct {
32
-	id         string
33
-	pid        int
34
-	hcsProcess hcsshim.Process
35
-}
36
-
37
-type container struct {
38
-	sync.Mutex
39
-
40
-	// The ociSpec is required, as client.Create() needs a spec, but can
41
-	// be called from the RestartManager context which does not otherwise
42
-	// have access to the Spec
43
-	ociSpec *specs.Spec
44
-
45
-	isWindows    bool
46
-	hcsContainer hcsshim.Container
47
-
48
-	id               string
49
-	status           Status
50
-	exitedAt         time.Time
51
-	exitCode         uint32
52
-	waitCh           chan struct{}
53
-	init             *process
54
-	execs            map[string]*process
55
-	terminateInvoked bool
56
-}
57
-
58
-// Win32 error codes that are used for various workarounds
59
-// These really should be ALL_CAPS to match golangs syscall library and standard
60
-// Win32 error conventions, but golint insists on CamelCase.
61
-const (
62
-	CoEClassstring     = syscall.Errno(0x800401F3) // Invalid class string
63
-	ErrorNoNetwork     = syscall.Errno(1222)       // The network is not present or not started
64
-	ErrorBadPathname   = syscall.Errno(161)        // The specified path is invalid
65
-	ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
66
-)
67
-
68
-// defaultOwner is a tag passed to HCS to allow it to differentiate between
69
-// container creator management stacks. We hard code "docker" in the case
70
-// of docker.
71
-const defaultOwner = "docker"
72
-
73
-type client struct {
74
-	sync.Mutex
75
-
76
-	stateDir   string
77
-	backend    Backend
78
-	logger     *logrus.Entry
79
-	eventQ     queue
80
-	containers map[string]*container
81
-}
82
-
83
-// NewClient creates a new local executor for windows
84
-func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b Backend) (Client, error) {
85
-	c := &client{
86
-		stateDir:   stateDir,
87
-		backend:    b,
88
-		logger:     logrus.WithField("module", "libcontainerd").WithField("module", "libcontainerd").WithField("namespace", ns),
89
-		containers: make(map[string]*container),
90
-	}
91
-
92
-	return c, nil
93
-}
94
-
95
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
96
-	return containerd.Version{}, errors.New("not implemented on Windows")
97
-}
98
-
99
-// Create is the entrypoint to create a container from a spec.
100
-// Table below shows the fields required for HCS JSON calling parameters,
101
-// where if not populated, is omitted.
102
-// +-----------------+--------------------------------------------+---------------------------------------------------+
103
-// |                 | Isolation=Process                          | Isolation=Hyper-V                                 |
104
-// +-----------------+--------------------------------------------+---------------------------------------------------+
105
-// | VolumePath      | \\?\\Volume{GUIDa}                         |                                                   |
106
-// | LayerFolderPath | %root%\windowsfilter\containerID           |                                                   |
107
-// | Layers[]        | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID        |
108
-// | HvRuntime       |                                            | ImagePath=%root%\BaseLayerID\UtilityVM            |
109
-// +-----------------+--------------------------------------------+---------------------------------------------------+
110
-//
111
-// Isolation=Process example:
112
-//
113
-// {
114
-//	"SystemType": "Container",
115
-//	"Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
116
-//	"Owner": "docker",
117
-//	"VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
118
-//	"IgnoreFlushesDuringBoot": true,
119
-//	"LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
120
-//	"Layers": [{
121
-//		"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
122
-//		"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
123
-//	}],
124
-//	"HostName": "5e0055c814a6",
125
-//	"MappedDirectories": [],
126
-//	"HvPartition": false,
127
-//	"EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
128
-//}
129
-//
130
-// Isolation=Hyper-V example:
131
-//
132
-//{
133
-//	"SystemType": "Container",
134
-//	"Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
135
-//	"Owner": "docker",
136
-//	"IgnoreFlushesDuringBoot": true,
137
-//	"Layers": [{
138
-//		"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
139
-//		"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
140
-//	}],
141
-//	"HostName": "475c2c58933b",
142
-//	"MappedDirectories": [],
143
-//	"HvPartition": true,
144
-//	"EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
145
-//	"DNSSearchList": "a.com,b.com,c.com",
146
-//	"HvRuntime": {
147
-//		"ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
148
-//	},
149
-//}
150
-func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeOptions interface{}) error {
151
-	if ctr := c.getContainer(id); ctr != nil {
152
-		return errors.WithStack(newConflictError("id already in use"))
153
-	}
154
-
155
-	// spec.Linux must be nil for Windows containers, but spec.Windows
156
-	// will be filled in regardless of container platform.  This is a
157
-	// temporary workaround due to LCOW requiring layer folder paths,
158
-	// which are stored under spec.Windows.
159
-	//
160
-	// TODO: @darrenstahlmsft fix this once the OCI spec is updated to
161
-	// support layer folder paths for LCOW
162
-	if spec.Linux == nil {
163
-		return c.createWindows(id, spec, runtimeOptions)
164
-	}
165
-	return c.createLinux(id, spec, runtimeOptions)
166
-}
167
-
168
-func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
169
-	logger := c.logger.WithField("container", id)
170
-	configuration := &hcsshim.ContainerConfig{
171
-		SystemType:              "Container",
172
-		Name:                    id,
173
-		Owner:                   defaultOwner,
174
-		IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
175
-		HostName:                spec.Hostname,
176
-		HvPartition:             false,
177
-	}
178
-
179
-	c.extractResourcesFromSpec(spec, configuration)
180
-
181
-	if spec.Windows.Resources != nil {
182
-		if spec.Windows.Resources.Storage != nil {
183
-			if spec.Windows.Resources.Storage.Bps != nil {
184
-				configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
185
-			}
186
-			if spec.Windows.Resources.Storage.Iops != nil {
187
-				configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
188
-			}
189
-		}
190
-	}
191
-
192
-	if spec.Windows.HyperV != nil {
193
-		configuration.HvPartition = true
194
-	}
195
-
196
-	if spec.Windows.Network != nil {
197
-		configuration.EndpointList = spec.Windows.Network.EndpointList
198
-		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
199
-		if spec.Windows.Network.DNSSearchList != nil {
200
-			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
201
-		}
202
-		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
203
-	}
204
-
205
-	if cs, ok := spec.Windows.CredentialSpec.(string); ok {
206
-		configuration.Credentials = cs
207
-	}
208
-
209
-	// We must have least two layers in the spec, the bottom one being a
210
-	// base image, the top one being the RW layer.
211
-	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
212
-		return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
213
-	}
214
-
215
-	// Strip off the top-most layer as that's passed in separately to HCS
216
-	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
217
-	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
218
-
219
-	if configuration.HvPartition {
220
-		// We don't currently support setting the utility VM image explicitly.
221
-		// TODO @swernli/jhowardmsft circa RS5, this may be re-locatable.
222
-		if spec.Windows.HyperV.UtilityVMPath != "" {
223
-			return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
224
-		}
225
-
226
-		// Find the upper-most utility VM image.
227
-		var uvmImagePath string
228
-		for _, path := range layerFolders {
229
-			fullPath := filepath.Join(path, "UtilityVM")
230
-			_, err := os.Stat(fullPath)
231
-			if err == nil {
232
-				uvmImagePath = fullPath
233
-				break
234
-			}
235
-			if !os.IsNotExist(err) {
236
-				return err
237
-			}
238
-		}
239
-		if uvmImagePath == "" {
240
-			return errors.New("utility VM image could not be found")
241
-		}
242
-		configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
243
-
244
-		if spec.Root.Path != "" {
245
-			return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
246
-		}
247
-	} else {
248
-		const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
249
-		if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
250
-			return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
251
-		}
252
-		// HCS API requires the trailing backslash to be removed
253
-		configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
254
-	}
255
-
256
-	if spec.Root.Readonly {
257
-		return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
258
-	}
259
-
260
-	for _, layerPath := range layerFolders {
261
-		_, filename := filepath.Split(layerPath)
262
-		g, err := hcsshim.NameToGuid(filename)
263
-		if err != nil {
264
-			return err
265
-		}
266
-		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
267
-			ID:   g.ToString(),
268
-			Path: layerPath,
269
-		})
270
-	}
271
-
272
-	// Add the mounts (volumes, bind mounts etc) to the structure
273
-	var mds []hcsshim.MappedDir
274
-	var mps []hcsshim.MappedPipe
275
-	for _, mount := range spec.Mounts {
276
-		const pipePrefix = `\\.\pipe\`
277
-		if mount.Type != "" {
278
-			return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
279
-		}
280
-		if strings.HasPrefix(mount.Destination, pipePrefix) {
281
-			mp := hcsshim.MappedPipe{
282
-				HostPath:          mount.Source,
283
-				ContainerPipeName: mount.Destination[len(pipePrefix):],
284
-			}
285
-			mps = append(mps, mp)
286
-		} else {
287
-			md := hcsshim.MappedDir{
288
-				HostPath:      mount.Source,
289
-				ContainerPath: mount.Destination,
290
-				ReadOnly:      false,
291
-			}
292
-			for _, o := range mount.Options {
293
-				if strings.ToLower(o) == "ro" {
294
-					md.ReadOnly = true
295
-				}
296
-			}
297
-			mds = append(mds, md)
298
-		}
299
-	}
300
-	configuration.MappedDirectories = mds
301
-	if len(mps) > 0 && system.GetOSVersion().Build < 16299 { // RS3
302
-		return errors.New("named pipe mounts are not supported on this version of Windows")
303
-	}
304
-	configuration.MappedPipes = mps
305
-
306
-	if len(spec.Windows.Devices) > 0 {
307
-		// Add any device assignments
308
-		if configuration.HvPartition {
309
-			return errors.New("device assignment is not supported for HyperV containers")
310
-		}
311
-		if system.GetOSVersion().Build < 17763 { // RS5
312
-			return errors.New("device assignment requires Windows builds RS5 (17763+) or later")
313
-		}
314
-		for _, d := range spec.Windows.Devices {
315
-			configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
316
-		}
317
-	}
318
-
319
-	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
320
-	if err != nil {
321
-		return err
322
-	}
323
-
324
-	// Construct a container object for calling start on it.
325
-	ctr := &container{
326
-		id:           id,
327
-		execs:        make(map[string]*process),
328
-		isWindows:    true,
329
-		ociSpec:      spec,
330
-		hcsContainer: hcsContainer,
331
-		status:       StatusCreated,
332
-		waitCh:       make(chan struct{}),
333
-	}
334
-
335
-	logger.Debug("starting container")
336
-	if err = hcsContainer.Start(); err != nil {
337
-		c.logger.WithError(err).Error("failed to start container")
338
-		ctr.Lock()
339
-		if err := c.terminateContainer(ctr); err != nil {
340
-			c.logger.WithError(err).Error("failed to cleanup after a failed Start")
341
-		} else {
342
-			c.logger.Debug("cleaned up after failed Start by calling Terminate")
343
-		}
344
-		ctr.Unlock()
345
-		return err
346
-	}
347
-
348
-	c.Lock()
349
-	c.containers[id] = ctr
350
-	c.Unlock()
351
-
352
-	logger.Debug("createWindows() completed successfully")
353
-	return nil
354
-
355
-}
356
-
357
-func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interface{}) error {
358
-	logrus.Debugf("libcontainerd: createLinux(): containerId %s ", id)
359
-	logger := c.logger.WithField("container", id)
360
-
361
-	if runtimeOptions == nil {
362
-		return fmt.Errorf("lcow option must be supplied to the runtime")
363
-	}
364
-	lcowConfig, ok := runtimeOptions.(*opengcs.Config)
365
-	if !ok {
366
-		return fmt.Errorf("lcow option must be supplied to the runtime")
367
-	}
368
-
369
-	configuration := &hcsshim.ContainerConfig{
370
-		HvPartition:                 true,
371
-		Name:                        id,
372
-		SystemType:                  "container",
373
-		ContainerType:               "linux",
374
-		Owner:                       defaultOwner,
375
-		TerminateOnLastHandleClosed: true,
376
-	}
377
-
378
-	if lcowConfig.ActualMode == opengcs.ModeActualVhdx {
379
-		configuration.HvRuntime = &hcsshim.HvRuntime{
380
-			ImagePath:          lcowConfig.Vhdx,
381
-			BootSource:         "Vhd",
382
-			WritableBootSource: false,
383
-		}
384
-	} else {
385
-		configuration.HvRuntime = &hcsshim.HvRuntime{
386
-			ImagePath:           lcowConfig.KirdPath,
387
-			LinuxKernelFile:     lcowConfig.KernelFile,
388
-			LinuxInitrdFile:     lcowConfig.InitrdFile,
389
-			LinuxBootParameters: lcowConfig.BootParameters,
390
-		}
391
-	}
392
-
393
-	if spec.Windows == nil {
394
-		return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
395
-	}
396
-
397
-	c.extractResourcesFromSpec(spec, configuration)
398
-
399
-	// We must have least one layer in the spec
400
-	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
401
-		return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
402
-	}
403
-
404
-	// Strip off the top-most layer as that's passed in separately to HCS
405
-	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
406
-	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
407
-
408
-	for _, layerPath := range layerFolders {
409
-		_, filename := filepath.Split(layerPath)
410
-		g, err := hcsshim.NameToGuid(filename)
411
-		if err != nil {
412
-			return err
413
-		}
414
-		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
415
-			ID:   g.ToString(),
416
-			Path: filepath.Join(layerPath, "layer.vhd"),
417
-		})
418
-	}
419
-
420
-	if spec.Windows.Network != nil {
421
-		configuration.EndpointList = spec.Windows.Network.EndpointList
422
-		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
423
-		if spec.Windows.Network.DNSSearchList != nil {
424
-			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
425
-		}
426
-		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
427
-	}
428
-
429
-	// Add the mounts (volumes, bind mounts etc) to the structure. We have to do
430
-	// some translation for both the mapped directories passed into HCS and in
431
-	// the spec.
432
-	//
433
-	// For HCS, we only pass in the mounts from the spec which are type "bind".
434
-	// Further, the "ContainerPath" field (which is a little mis-leadingly
435
-	// named when it applies to the utility VM rather than the container in the
436
-	// utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed
437
-	// by the caller through a 'uvmpath' option.
438
-	//
439
-	// We do similar translation for the mounts in the spec by stripping out
440
-	// the uvmpath option, and translating the Source path to the location in the
441
-	// utility VM calculated above.
442
-	//
443
-	// From inside the utility VM, you would see a 9p mount such as in the following
444
-	// where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds
445
-	// specifically:
446
-	//
447
-	//	/ # mount
448
-	//	rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934)
449
-	//	proc on /proc type proc (rw,relatime)
450
-	//	sysfs on /sys type sysfs (rw,relatime)
451
-	//	udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755)
452
-	//	tmpfs on /run type tmpfs (rw,relatime)
453
-	//	cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma)
454
-	//	mqueue on /dev/mqueue type mqueue (rw,relatime)
455
-	//	devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
456
-	//	/binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6)
457
-	//	/dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
458
-	//	/dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
459
-	//	overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work)
460
-	//
461
-	//  /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l
462
-	//	total 16
463
-	//	drwx------    3 0        0               60 Sep  7 18:54 binds
464
-	//	-rw-r--r--    1 0        0             3345 Sep  7 18:54 config.json
465
-	//	drwxr-xr-x   10 0        0             4096 Sep  6 17:26 layer0
466
-	//	drwxr-xr-x    1 0        0             4096 Sep  7 18:54 rootfs
467
-	//	drwxr-xr-x    5 0        0             4096 Sep  7 18:54 scratch
468
-	//
469
-	//	/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds
470
-	//	total 0
471
-	//	drwxrwxrwt    2 0        0             4096 Sep  7 16:51 target
472
-
473
-	mds := []hcsshim.MappedDir{}
474
-	specMounts := []specs.Mount{}
475
-	for _, mount := range spec.Mounts {
476
-		specMount := mount
477
-		if mount.Type == "bind" {
478
-			// Strip out the uvmpath from the options
479
-			updatedOptions := []string{}
480
-			uvmPath := ""
481
-			readonly := false
482
-			for _, opt := range mount.Options {
483
-				dropOption := false
484
-				elements := strings.SplitN(opt, "=", 2)
485
-				switch elements[0] {
486
-				case "uvmpath":
487
-					uvmPath = elements[1]
488
-					dropOption = true
489
-				case "rw":
490
-				case "ro":
491
-					readonly = true
492
-				case "rbind":
493
-				default:
494
-					return fmt.Errorf("unsupported option %q", opt)
495
-				}
496
-				if !dropOption {
497
-					updatedOptions = append(updatedOptions, opt)
498
-				}
499
-			}
500
-			mount.Options = updatedOptions
501
-			if uvmPath == "" {
502
-				return fmt.Errorf("no uvmpath for bind mount %+v", mount)
503
-			}
504
-			md := hcsshim.MappedDir{
505
-				HostPath:          mount.Source,
506
-				ContainerPath:     path.Join(uvmPath, mount.Destination),
507
-				CreateInUtilityVM: true,
508
-				ReadOnly:          readonly,
509
-			}
510
-			// If we are 1803/RS4+ enable LinuxMetadata support by default
511
-			if system.GetOSVersion().Build >= 17134 {
512
-				md.LinuxMetadata = true
513
-			}
514
-			mds = append(mds, md)
515
-			specMount.Source = path.Join(uvmPath, mount.Destination)
516
-		}
517
-		specMounts = append(specMounts, specMount)
518
-	}
519
-	configuration.MappedDirectories = mds
520
-
521
-	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
522
-	if err != nil {
523
-		return err
524
-	}
525
-
526
-	spec.Mounts = specMounts
527
-
528
-	// Construct a container object for calling start on it.
529
-	ctr := &container{
530
-		id:           id,
531
-		execs:        make(map[string]*process),
532
-		isWindows:    false,
533
-		ociSpec:      spec,
534
-		hcsContainer: hcsContainer,
535
-		status:       StatusCreated,
536
-		waitCh:       make(chan struct{}),
537
-	}
538
-
539
-	// Start the container.
540
-	logger.Debug("starting container")
541
-	if err = hcsContainer.Start(); err != nil {
542
-		c.logger.WithError(err).Error("failed to start container")
543
-		ctr.debugGCS()
544
-		ctr.Lock()
545
-		if err := c.terminateContainer(ctr); err != nil {
546
-			c.logger.WithError(err).Error("failed to cleanup after a failed Start")
547
-		} else {
548
-			c.logger.Debug("cleaned up after failed Start by calling Terminate")
549
-		}
550
-		ctr.Unlock()
551
-		return err
552
-	}
553
-	ctr.debugGCS()
554
-
555
-	c.Lock()
556
-	c.containers[id] = ctr
557
-	c.Unlock()
558
-
559
-	c.eventQ.append(id, func() {
560
-		ei := EventInfo{
561
-			ContainerID: id,
562
-		}
563
-		c.logger.WithFields(logrus.Fields{
564
-			"container": ctr.id,
565
-			"event":     EventCreate,
566
-		}).Info("sending event")
567
-		err := c.backend.ProcessEvent(id, EventCreate, ei)
568
-		if err != nil {
569
-			c.logger.WithError(err).WithFields(logrus.Fields{
570
-				"container": id,
571
-				"event":     EventCreate,
572
-			}).Error("failed to process event")
573
-		}
574
-	})
575
-
576
-	logger.Debug("createLinux() completed successfully")
577
-	return nil
578
-}
579
-
580
-func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
581
-	if spec.Windows.Resources != nil {
582
-		if spec.Windows.Resources.CPU != nil {
583
-			if spec.Windows.Resources.CPU.Count != nil {
584
-				// This check is being done here rather than in adaptContainerSettings
585
-				// because we don't want to update the HostConfig in case this container
586
-				// is moved to a host with more CPUs than this one.
587
-				cpuCount := *spec.Windows.Resources.CPU.Count
588
-				hostCPUCount := uint64(sysinfo.NumCPU())
589
-				if cpuCount > hostCPUCount {
590
-					c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
591
-					cpuCount = hostCPUCount
592
-				}
593
-				configuration.ProcessorCount = uint32(cpuCount)
594
-			}
595
-			if spec.Windows.Resources.CPU.Shares != nil {
596
-				configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
597
-			}
598
-			if spec.Windows.Resources.CPU.Maximum != nil {
599
-				configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum)
600
-			}
601
-		}
602
-		if spec.Windows.Resources.Memory != nil {
603
-			if spec.Windows.Resources.Memory.Limit != nil {
604
-				configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
605
-			}
606
-		}
607
-	}
608
-}
609
-
610
-func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachStdio StdioCallback) (int, error) {
611
-	ctr := c.getContainer(id)
612
-	switch {
613
-	case ctr == nil:
614
-		return -1, errors.WithStack(newNotFoundError("no such container"))
615
-	case ctr.init != nil:
616
-		return -1, errors.WithStack(newConflictError("container already started"))
617
-	}
618
-
619
-	logger := c.logger.WithField("container", id)
620
-
621
-	// Note we always tell HCS to create stdout as it's required
622
-	// regardless of '-i' or '-t' options, so that docker can always grab
623
-	// the output through logs. We also tell HCS to always create stdin,
624
-	// even if it's not used - it will be closed shortly. Stderr is only
625
-	// created if it we're not -t.
626
-	var (
627
-		emulateConsole   bool
628
-		createStdErrPipe bool
629
-	)
630
-	if ctr.ociSpec.Process != nil {
631
-		emulateConsole = ctr.ociSpec.Process.Terminal
632
-		createStdErrPipe = !ctr.ociSpec.Process.Terminal
633
-	}
634
-
635
-	createProcessParms := &hcsshim.ProcessConfig{
636
-		EmulateConsole:   emulateConsole,
637
-		WorkingDirectory: ctr.ociSpec.Process.Cwd,
638
-		CreateStdInPipe:  true,
639
-		CreateStdOutPipe: true,
640
-		CreateStdErrPipe: createStdErrPipe,
641
-	}
642
-
643
-	if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
644
-		createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
645
-		createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
646
-	}
647
-
648
-	// Configure the environment for the process
649
-	createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
650
-	if ctr.isWindows {
651
-		createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
652
-	} else {
653
-		createProcessParms.CommandArgs = ctr.ociSpec.Process.Args
654
-	}
655
-	createProcessParms.User = ctr.ociSpec.Process.User.Username
656
-
657
-	// LCOW requires the raw OCI spec passed through HCS and onwards to
658
-	// GCS for the utility VM.
659
-	if !ctr.isWindows {
660
-		ociBuf, err := json.Marshal(ctr.ociSpec)
661
-		if err != nil {
662
-			return -1, err
663
-		}
664
-		ociRaw := json.RawMessage(ociBuf)
665
-		createProcessParms.OCISpecification = &ociRaw
666
-	}
667
-
668
-	ctr.Lock()
669
-	defer ctr.Unlock()
670
-
671
-	// Start the command running in the container.
672
-	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
673
-	if err != nil {
674
-		logger.WithError(err).Error("CreateProcess() failed")
675
-		return -1, err
676
-	}
677
-	defer func() {
678
-		if err != nil {
679
-			if err := newProcess.Kill(); err != nil {
680
-				logger.WithError(err).Error("failed to kill process")
681
-			}
682
-			go func() {
683
-				if err := newProcess.Wait(); err != nil {
684
-					logger.WithError(err).Error("failed to wait for process")
685
-				}
686
-				if err := newProcess.Close(); err != nil {
687
-					logger.WithError(err).Error("failed to clean process resources")
688
-				}
689
-			}()
690
-		}
691
-	}()
692
-	p := &process{
693
-		hcsProcess: newProcess,
694
-		id:         InitProcessName,
695
-		pid:        newProcess.Pid(),
696
-	}
697
-	logger.WithField("pid", p.pid).Debug("init process started")
698
-
699
-	dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal)
700
-	if err != nil {
701
-		logger.WithError(err).Error("failed to get stdio pipes")
702
-		return -1, err
703
-	}
704
-	_, err = attachStdio(dio)
705
-	if err != nil {
706
-		logger.WithError(err).Error("failed to attache stdio")
707
-		return -1, err
708
-	}
709
-	ctr.status = StatusRunning
710
-	ctr.init = p
711
-
712
-	// Spin up a go routine waiting for exit to handle cleanup
713
-	go c.reapProcess(ctr, p)
714
-
715
-	// Generate the associated event
716
-	c.eventQ.append(id, func() {
717
-		ei := EventInfo{
718
-			ContainerID: id,
719
-			ProcessID:   InitProcessName,
720
-			Pid:         uint32(p.pid),
721
-		}
722
-		c.logger.WithFields(logrus.Fields{
723
-			"container":  ctr.id,
724
-			"event":      EventStart,
725
-			"event-info": ei,
726
-		}).Info("sending event")
727
-		err := c.backend.ProcessEvent(ei.ContainerID, EventStart, ei)
728
-		if err != nil {
729
-			c.logger.WithError(err).WithFields(logrus.Fields{
730
-				"container":  id,
731
-				"event":      EventStart,
732
-				"event-info": ei,
733
-			}).Error("failed to process event")
734
-		}
735
-	})
736
-	logger.Debug("start() completed")
737
-	return p.pid, nil
738
-}
739
-
740
-func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
741
-	stdin, stdout, stderr, err := newProcess.Stdio()
742
-	if err != nil {
743
-		return nil, err
744
-	}
745
-
746
-	dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal)
747
-
748
-	// Convert io.ReadClosers to io.Readers
749
-	if stdout != nil {
750
-		dio.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
751
-	}
752
-	if stderr != nil {
753
-		dio.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
754
-	}
755
-	return dio, nil
756
-}
757
-
758
-// Exec adds a process in an running container
759
-func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) {
760
-	ctr := c.getContainer(containerID)
761
-	switch {
762
-	case ctr == nil:
763
-		return -1, errors.WithStack(newNotFoundError("no such container"))
764
-	case ctr.hcsContainer == nil:
765
-		return -1, errors.WithStack(newInvalidParameterError("container is not running"))
766
-	case ctr.execs != nil && ctr.execs[processID] != nil:
767
-		return -1, errors.WithStack(newConflictError("id already in use"))
768
-	}
769
-	logger := c.logger.WithFields(logrus.Fields{
770
-		"container": containerID,
771
-		"exec":      processID,
772
-	})
773
-
774
-	// Note we always tell HCS to
775
-	// create stdout as it's required regardless of '-i' or '-t' options, so that
776
-	// docker can always grab the output through logs. We also tell HCS to always
777
-	// create stdin, even if it's not used - it will be closed shortly. Stderr
778
-	// is only created if it we're not -t.
779
-	createProcessParms := hcsshim.ProcessConfig{
780
-		CreateStdInPipe:  true,
781
-		CreateStdOutPipe: true,
782
-		CreateStdErrPipe: !spec.Terminal,
783
-	}
784
-	if spec.Terminal {
785
-		createProcessParms.EmulateConsole = true
786
-		if spec.ConsoleSize != nil {
787
-			createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height)
788
-			createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width)
789
-		}
790
-	}
791
-
792
-	// Take working directory from the process to add if it is defined,
793
-	// otherwise take from the first process.
794
-	if spec.Cwd != "" {
795
-		createProcessParms.WorkingDirectory = spec.Cwd
796
-	} else {
797
-		createProcessParms.WorkingDirectory = ctr.ociSpec.Process.Cwd
798
-	}
799
-
800
-	// Configure the environment for the process
801
-	createProcessParms.Environment = setupEnvironmentVariables(spec.Env)
802
-	if ctr.isWindows {
803
-		createProcessParms.CommandLine = strings.Join(spec.Args, " ")
804
-	} else {
805
-		createProcessParms.CommandArgs = spec.Args
806
-	}
807
-	createProcessParms.User = spec.User.Username
808
-
809
-	logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
810
-
811
-	// Start the command running in the container.
812
-	newProcess, err := ctr.hcsContainer.CreateProcess(&createProcessParms)
813
-	if err != nil {
814
-		logger.WithError(err).Errorf("exec's CreateProcess() failed")
815
-		return -1, err
816
-	}
817
-	pid := newProcess.Pid()
818
-	defer func() {
819
-		if err != nil {
820
-			if err := newProcess.Kill(); err != nil {
821
-				logger.WithError(err).Error("failed to kill process")
822
-			}
823
-			go func() {
824
-				if err := newProcess.Wait(); err != nil {
825
-					logger.WithError(err).Error("failed to wait for process")
826
-				}
827
-				if err := newProcess.Close(); err != nil {
828
-					logger.WithError(err).Error("failed to clean process resources")
829
-				}
830
-			}()
831
-		}
832
-	}()
833
-
834
-	dio, err := newIOFromProcess(newProcess, spec.Terminal)
835
-	if err != nil {
836
-		logger.WithError(err).Error("failed to get stdio pipes")
837
-		return -1, err
838
-	}
839
-	// Tell the engine to attach streams back to the client
840
-	_, err = attachStdio(dio)
841
-	if err != nil {
842
-		return -1, err
843
-	}
844
-
845
-	p := &process{
846
-		id:         processID,
847
-		pid:        pid,
848
-		hcsProcess: newProcess,
849
-	}
850
-
851
-	// Add the process to the container's list of processes
852
-	ctr.Lock()
853
-	ctr.execs[processID] = p
854
-	ctr.Unlock()
855
-
856
-	// Spin up a go routine waiting for exit to handle cleanup
857
-	go c.reapProcess(ctr, p)
858
-
859
-	c.eventQ.append(ctr.id, func() {
860
-		ei := EventInfo{
861
-			ContainerID: ctr.id,
862
-			ProcessID:   p.id,
863
-			Pid:         uint32(p.pid),
864
-		}
865
-		c.logger.WithFields(logrus.Fields{
866
-			"container":  ctr.id,
867
-			"event":      EventExecAdded,
868
-			"event-info": ei,
869
-		}).Info("sending event")
870
-		err := c.backend.ProcessEvent(ctr.id, EventExecAdded, ei)
871
-		if err != nil {
872
-			c.logger.WithError(err).WithFields(logrus.Fields{
873
-				"container":  ctr.id,
874
-				"event":      EventExecAdded,
875
-				"event-info": ei,
876
-			}).Error("failed to process event")
877
-		}
878
-		err = c.backend.ProcessEvent(ctr.id, EventExecStarted, ei)
879
-		if err != nil {
880
-			c.logger.WithError(err).WithFields(logrus.Fields{
881
-				"container":  ctr.id,
882
-				"event":      EventExecStarted,
883
-				"event-info": ei,
884
-			}).Error("failed to process event")
885
-		}
886
-	})
887
-
888
-	return pid, nil
889
-}
890
-
891
-// Signal handles `docker stop` on Windows. While Linux has support for
892
-// the full range of signals, signals aren't really implemented on Windows.
893
-// We fake supporting regular stop and -9 to force kill.
894
-func (c *client) SignalProcess(_ context.Context, containerID, processID string, signal int) error {
895
-	ctr, p, err := c.getProcess(containerID, processID)
896
-	if err != nil {
897
-		return err
898
-	}
899
-
900
-	logger := c.logger.WithFields(logrus.Fields{
901
-		"container": containerID,
902
-		"process":   processID,
903
-		"pid":       p.pid,
904
-		"signal":    signal,
905
-	})
906
-	logger.Debug("Signal()")
907
-
908
-	if processID == InitProcessName {
909
-		if syscall.Signal(signal) == syscall.SIGKILL {
910
-			// Terminate the compute system
911
-			ctr.Lock()
912
-			ctr.terminateInvoked = true
913
-			if err := ctr.hcsContainer.Terminate(); err != nil {
914
-				if !hcsshim.IsPending(err) {
915
-					logger.WithError(err).Error("failed to terminate hccshim container")
916
-				}
917
-			}
918
-			ctr.Unlock()
919
-		} else {
920
-			// Shut down the container
921
-			if err := ctr.hcsContainer.Shutdown(); err != nil {
922
-				if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
923
-					// ignore errors
924
-					logger.WithError(err).Error("failed to shutdown hccshim container")
925
-				}
926
-			}
927
-		}
928
-	} else {
929
-		return p.hcsProcess.Kill()
930
-	}
931
-
932
-	return nil
933
-}
934
-
935
-// Resize handles a CLI event to resize an interactive docker run or docker
936
-// exec window.
937
-func (c *client) ResizeTerminal(_ context.Context, containerID, processID string, width, height int) error {
938
-	_, p, err := c.getProcess(containerID, processID)
939
-	if err != nil {
940
-		return err
941
-	}
942
-
943
-	c.logger.WithFields(logrus.Fields{
944
-		"container": containerID,
945
-		"process":   processID,
946
-		"height":    height,
947
-		"width":     width,
948
-		"pid":       p.pid,
949
-	}).Debug("resizing")
950
-	return p.hcsProcess.ResizeConsole(uint16(width), uint16(height))
951
-}
952
-
953
-func (c *client) CloseStdin(_ context.Context, containerID, processID string) error {
954
-	_, p, err := c.getProcess(containerID, processID)
955
-	if err != nil {
956
-		return err
957
-	}
958
-
959
-	return p.hcsProcess.CloseStdin()
960
-}
961
-
962
-// Pause handles pause requests for containers
963
-func (c *client) Pause(_ context.Context, containerID string) error {
964
-	ctr, _, err := c.getProcess(containerID, InitProcessName)
965
-	if err != nil {
966
-		return err
967
-	}
968
-
969
-	if ctr.ociSpec.Windows.HyperV == nil {
970
-		return errors.New("cannot pause Windows Server Containers")
971
-	}
972
-
973
-	ctr.Lock()
974
-	defer ctr.Unlock()
975
-
976
-	if err = ctr.hcsContainer.Pause(); err != nil {
977
-		return err
978
-	}
979
-
980
-	ctr.status = StatusPaused
981
-
982
-	c.eventQ.append(containerID, func() {
983
-		err := c.backend.ProcessEvent(containerID, EventPaused, EventInfo{
984
-			ContainerID: containerID,
985
-			ProcessID:   InitProcessName,
986
-		})
987
-		c.logger.WithFields(logrus.Fields{
988
-			"container": ctr.id,
989
-			"event":     EventPaused,
990
-		}).Info("sending event")
991
-		if err != nil {
992
-			c.logger.WithError(err).WithFields(logrus.Fields{
993
-				"container": containerID,
994
-				"event":     EventPaused,
995
-			}).Error("failed to process event")
996
-		}
997
-	})
998
-
999
-	return nil
1000
-}
1001
-
1002
-// Resume handles resume requests for containers
1003
-func (c *client) Resume(_ context.Context, containerID string) error {
1004
-	ctr, _, err := c.getProcess(containerID, InitProcessName)
1005
-	if err != nil {
1006
-		return err
1007
-	}
1008
-
1009
-	if ctr.ociSpec.Windows.HyperV == nil {
1010
-		return errors.New("cannot resume Windows Server Containers")
1011
-	}
1012
-
1013
-	ctr.Lock()
1014
-	defer ctr.Unlock()
1015
-
1016
-	if err = ctr.hcsContainer.Resume(); err != nil {
1017
-		return err
1018
-	}
1019
-
1020
-	ctr.status = StatusRunning
1021
-
1022
-	c.eventQ.append(containerID, func() {
1023
-		err := c.backend.ProcessEvent(containerID, EventResumed, EventInfo{
1024
-			ContainerID: containerID,
1025
-			ProcessID:   InitProcessName,
1026
-		})
1027
-		c.logger.WithFields(logrus.Fields{
1028
-			"container": ctr.id,
1029
-			"event":     EventResumed,
1030
-		}).Info("sending event")
1031
-		if err != nil {
1032
-			c.logger.WithError(err).WithFields(logrus.Fields{
1033
-				"container": containerID,
1034
-				"event":     EventResumed,
1035
-			}).Error("failed to process event")
1036
-		}
1037
-	})
1038
-
1039
-	return nil
1040
-}
1041
-
1042
-// Stats handles stats requests for containers
1043
-func (c *client) Stats(_ context.Context, containerID string) (*Stats, error) {
1044
-	ctr, _, err := c.getProcess(containerID, InitProcessName)
1045
-	if err != nil {
1046
-		return nil, err
1047
-	}
1048
-
1049
-	readAt := time.Now()
1050
-	s, err := ctr.hcsContainer.Statistics()
1051
-	if err != nil {
1052
-		return nil, err
1053
-	}
1054
-	return &Stats{
1055
-		Read:     readAt,
1056
-		HCSStats: &s,
1057
-	}, nil
1058
-}
1059
-
1060
-// Restore is the handler for restoring a container
1061
-func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (bool, int, error) {
1062
-	c.logger.WithField("container", id).Debug("restore()")
1063
-
1064
-	// TODO Windows: On RS1, a re-attach isn't possible.
1065
-	// However, there is a scenario in which there is an issue.
1066
-	// Consider a background container. The daemon dies unexpectedly.
1067
-	// HCS will still have the compute service alive and running.
1068
-	// For consistence, we call in to shoot it regardless if HCS knows about it
1069
-	// We explicitly just log a warning if the terminate fails.
1070
-	// Then we tell the backend the container exited.
1071
-	if hc, err := hcsshim.OpenContainer(id); err == nil {
1072
-		const terminateTimeout = time.Minute * 2
1073
-		err := hc.Terminate()
1074
-
1075
-		if hcsshim.IsPending(err) {
1076
-			err = hc.WaitTimeout(terminateTimeout)
1077
-		} else if hcsshim.IsAlreadyStopped(err) {
1078
-			err = nil
1079
-		}
1080
-
1081
-		if err != nil {
1082
-			c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
1083
-			return false, -1, err
1084
-		}
1085
-	}
1086
-	return false, -1, nil
1087
-}
1088
-
1089
-// GetPidsForContainer returns a list of process IDs running in a container.
1090
-// Not used on Windows.
1091
-func (c *client) ListPids(_ context.Context, _ string) ([]uint32, error) {
1092
-	return nil, errors.New("not implemented on Windows")
1093
-}
1094
-
1095
-// Summary returns a summary of the processes running in a container.
1096
-// This is present in Windows to support docker top. In linux, the
1097
-// engine shells out to ps to get process information. On Windows, as
1098
-// the containers could be Hyper-V containers, they would not be
1099
-// visible on the container host. However, libcontainerd does have
1100
-// that information.
1101
-func (c *client) Summary(_ context.Context, containerID string) ([]Summary, error) {
1102
-	ctr, _, err := c.getProcess(containerID, InitProcessName)
1103
-	if err != nil {
1104
-		return nil, err
1105
-	}
1106
-
1107
-	p, err := ctr.hcsContainer.ProcessList()
1108
-	if err != nil {
1109
-		return nil, err
1110
-	}
1111
-
1112
-	pl := make([]Summary, len(p))
1113
-	for i := range p {
1114
-		pl[i] = Summary(p[i])
1115
-	}
1116
-	return pl, nil
1117
-}
1118
-
1119
-func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
1120
-	ec := -1
1121
-	ctr := c.getContainer(containerID)
1122
-	if ctr == nil {
1123
-		return uint32(ec), time.Now(), errors.WithStack(newNotFoundError("no such container"))
1124
-	}
1125
-
1126
-	select {
1127
-	case <-ctx.Done():
1128
-		return uint32(ec), time.Now(), errors.WithStack(ctx.Err())
1129
-	case <-ctr.waitCh:
1130
-	default:
1131
-		return uint32(ec), time.Now(), errors.New("container is not stopped")
1132
-	}
1133
-
1134
-	ctr.Lock()
1135
-	defer ctr.Unlock()
1136
-	return ctr.exitCode, ctr.exitedAt, nil
1137
-}
1138
-
1139
-func (c *client) Delete(_ context.Context, containerID string) error {
1140
-	c.Lock()
1141
-	defer c.Unlock()
1142
-	ctr := c.containers[containerID]
1143
-	if ctr == nil {
1144
-		return errors.WithStack(newNotFoundError("no such container"))
1145
-	}
1146
-
1147
-	ctr.Lock()
1148
-	defer ctr.Unlock()
1149
-
1150
-	switch ctr.status {
1151
-	case StatusCreated:
1152
-		if err := c.shutdownContainer(ctr); err != nil {
1153
-			return err
1154
-		}
1155
-		fallthrough
1156
-	case StatusStopped:
1157
-		delete(c.containers, containerID)
1158
-		return nil
1159
-	}
1160
-
1161
-	return errors.WithStack(newInvalidParameterError("container is not stopped"))
1162
-}
1163
-
1164
-func (c *client) Status(ctx context.Context, containerID string) (Status, error) {
1165
-	c.Lock()
1166
-	defer c.Unlock()
1167
-	ctr := c.containers[containerID]
1168
-	if ctr == nil {
1169
-		return StatusUnknown, errors.WithStack(newNotFoundError("no such container"))
1170
-	}
1171
-
1172
-	ctr.Lock()
1173
-	defer ctr.Unlock()
1174
-	return ctr.status, nil
1175
-}
1176
-
1177
-func (c *client) UpdateResources(ctx context.Context, containerID string, resources *Resources) error {
1178
-	// Updating resource isn't supported on Windows
1179
-	// but we should return nil for enabling updating container
1180
-	return nil
1181
-}
1182
-
1183
-func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
1184
-	return errors.New("Windows: Containers do not support checkpoints")
1185
-}
1186
-
1187
-func (c *client) getContainer(id string) *container {
1188
-	c.Lock()
1189
-	ctr := c.containers[id]
1190
-	c.Unlock()
1191
-
1192
-	return ctr
1193
-}
1194
-
1195
-func (c *client) getProcess(containerID, processID string) (*container, *process, error) {
1196
-	ctr := c.getContainer(containerID)
1197
-	switch {
1198
-	case ctr == nil:
1199
-		return nil, nil, errors.WithStack(newNotFoundError("no such container"))
1200
-	case ctr.init == nil:
1201
-		return nil, nil, errors.WithStack(newNotFoundError("container is not running"))
1202
-	case processID == InitProcessName:
1203
-		return ctr, ctr.init, nil
1204
-	default:
1205
-		ctr.Lock()
1206
-		defer ctr.Unlock()
1207
-		if ctr.execs == nil {
1208
-			return nil, nil, errors.WithStack(newNotFoundError("no execs"))
1209
-		}
1210
-	}
1211
-
1212
-	p := ctr.execs[processID]
1213
-	if p == nil {
1214
-		return nil, nil, errors.WithStack(newNotFoundError("no such exec"))
1215
-	}
1216
-
1217
-	return ctr, p, nil
1218
-}
1219
-
1220
-// ctr mutex must be held when calling this function.
1221
-func (c *client) shutdownContainer(ctr *container) error {
1222
-	var err error
1223
-	const waitTimeout = time.Minute * 5
1224
-
1225
-	if !ctr.terminateInvoked {
1226
-		err = ctr.hcsContainer.Shutdown()
1227
-	}
1228
-
1229
-	if hcsshim.IsPending(err) || ctr.terminateInvoked {
1230
-		err = ctr.hcsContainer.WaitTimeout(waitTimeout)
1231
-	} else if hcsshim.IsAlreadyStopped(err) {
1232
-		err = nil
1233
-	}
1234
-
1235
-	if err != nil {
1236
-		c.logger.WithError(err).WithField("container", ctr.id).
1237
-			Debug("failed to shutdown container, terminating it")
1238
-		terminateErr := c.terminateContainer(ctr)
1239
-		if terminateErr != nil {
1240
-			c.logger.WithError(terminateErr).WithField("container", ctr.id).
1241
-				Error("failed to shutdown container, and subsequent terminate also failed")
1242
-			return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
1243
-		}
1244
-		return err
1245
-	}
1246
-
1247
-	return nil
1248
-}
1249
-
1250
-// ctr mutex must be held when calling this function.
1251
-func (c *client) terminateContainer(ctr *container) error {
1252
-	const terminateTimeout = time.Minute * 5
1253
-	ctr.terminateInvoked = true
1254
-	err := ctr.hcsContainer.Terminate()
1255
-
1256
-	if hcsshim.IsPending(err) {
1257
-		err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
1258
-	} else if hcsshim.IsAlreadyStopped(err) {
1259
-		err = nil
1260
-	}
1261
-
1262
-	if err != nil {
1263
-		c.logger.WithError(err).WithField("container", ctr.id).
1264
-			Debug("failed to terminate container")
1265
-		return err
1266
-	}
1267
-
1268
-	return nil
1269
-}
1270
-
1271
-func (c *client) reapProcess(ctr *container, p *process) int {
1272
-	logger := c.logger.WithFields(logrus.Fields{
1273
-		"container": ctr.id,
1274
-		"process":   p.id,
1275
-	})
1276
-
1277
-	var eventErr error
1278
-
1279
-	// Block indefinitely for the process to exit.
1280
-	if err := p.hcsProcess.Wait(); err != nil {
1281
-		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1282
-			logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
1283
-		}
1284
-		// Fall through here, do not return. This ensures we attempt to
1285
-		// continue the shutdown in HCS and tell the docker engine that the
1286
-		// process/container has exited to avoid a container being dropped on
1287
-		// the floor.
1288
-	}
1289
-	exitedAt := time.Now()
1290
-
1291
-	exitCode, err := p.hcsProcess.ExitCode()
1292
-	if err != nil {
1293
-		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1294
-			logger.WithError(err).Warnf("unable to get exit code for process")
1295
-		}
1296
-		// Since we got an error retrieving the exit code, make sure that the
1297
-		// code we return doesn't incorrectly indicate success.
1298
-		exitCode = -1
1299
-
1300
-		// Fall through here, do not return. This ensures we attempt to
1301
-		// continue the shutdown in HCS and tell the docker engine that the
1302
-		// process/container has exited to avoid a container being dropped on
1303
-		// the floor.
1304
-	}
1305
-
1306
-	if err := p.hcsProcess.Close(); err != nil {
1307
-		logger.WithError(err).Warnf("failed to cleanup hcs process resources")
1308
-		exitCode = -1
1309
-		eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
1310
-	}
1311
-
1312
-	if p.id == InitProcessName {
1313
-		// Update container status
1314
-		ctr.Lock()
1315
-		ctr.status = StatusStopped
1316
-		ctr.exitedAt = exitedAt
1317
-		ctr.exitCode = uint32(exitCode)
1318
-		close(ctr.waitCh)
1319
-
1320
-		if err := c.shutdownContainer(ctr); err != nil {
1321
-			exitCode = -1
1322
-			logger.WithError(err).Warn("failed to shutdown container")
1323
-			thisErr := fmt.Errorf("failed to shutdown container: %s", err)
1324
-			if eventErr != nil {
1325
-				eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
1326
-			} else {
1327
-				eventErr = thisErr
1328
-			}
1329
-		} else {
1330
-			logger.Debug("completed container shutdown")
1331
-		}
1332
-		ctr.Unlock()
1333
-
1334
-		if err := ctr.hcsContainer.Close(); err != nil {
1335
-			exitCode = -1
1336
-			logger.WithError(err).Error("failed to clean hcs container resources")
1337
-			thisErr := fmt.Errorf("failed to terminate container: %s", err)
1338
-			if eventErr != nil {
1339
-				eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
1340
-			} else {
1341
-				eventErr = thisErr
1342
-			}
1343
-		}
1344
-	}
1345
-
1346
-	c.eventQ.append(ctr.id, func() {
1347
-		ei := EventInfo{
1348
-			ContainerID: ctr.id,
1349
-			ProcessID:   p.id,
1350
-			Pid:         uint32(p.pid),
1351
-			ExitCode:    uint32(exitCode),
1352
-			ExitedAt:    exitedAt,
1353
-			Error:       eventErr,
1354
-		}
1355
-		c.logger.WithFields(logrus.Fields{
1356
-			"container":  ctr.id,
1357
-			"event":      EventExit,
1358
-			"event-info": ei,
1359
-		}).Info("sending event")
1360
-		err := c.backend.ProcessEvent(ctr.id, EventExit, ei)
1361
-		if err != nil {
1362
-			c.logger.WithError(err).WithFields(logrus.Fields{
1363
-				"container":  ctr.id,
1364
-				"event":      EventExit,
1365
-				"event-info": ei,
1366
-			}).Error("failed to process event")
1367
-		}
1368
-		if p.id != InitProcessName {
1369
-			ctr.Lock()
1370
-			delete(ctr.execs, p.id)
1371
-			ctr.Unlock()
1372
-		}
1373
-	})
1374
-
1375
-	return exitCode
1376
-}
1377 1
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"errors"
5
-
6
-	"github.com/docker/docker/errdefs"
7
-)
8
-
9
-func newNotFoundError(err string) error { return errdefs.NotFound(errors.New(err)) }
10
-
11
-func newInvalidParameterError(err string) error { return errdefs.InvalidParameter(errors.New(err)) }
12
-
13
-func newConflictError(err string) error { return errdefs.Conflict(errors.New(err)) }
14 1
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package libcontainerd // import "github.com/docker/docker/libcontainerd"
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/containerd/containerd"
6
+	"github.com/docker/docker/libcontainerd/remote"
7
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
8
+)
9
+
10
+// NewClient creates a new libcontainerd client from a containerd client
11
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
12
+	return remote.NewClient(ctx, cli, stateDir, ns, b)
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package libcontainerd // import "github.com/docker/docker/libcontainerd"
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/containerd/containerd"
6
+	"github.com/docker/docker/libcontainerd/local"
7
+	"github.com/docker/docker/libcontainerd/remote"
8
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
9
+	"github.com/docker/docker/pkg/system"
10
+)
11
+
12
+// NewClient creates a new libcontainerd client from a containerd client
13
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
14
+	if !system.ContainerdRuntimeSupported() {
15
+		return local.NewClient(ctx, cli, stateDir, ns, b)
16
+	}
17
+	return remote.NewClient(ctx, cli, stateDir, ns, b)
18
+}
0 19
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package local // import "github.com/docker/docker/libcontainerd/local"
1
+
2
+import (
3
+	"io"
4
+	"sync"
5
+
6
+	"github.com/Microsoft/hcsshim"
7
+	"github.com/docker/docker/pkg/ioutils"
8
+)
9
+
10
+type autoClosingReader struct {
11
+	io.ReadCloser
12
+	sync.Once
13
+}
14
+
15
+func (r *autoClosingReader) Read(b []byte) (n int, err error) {
16
+	n, err = r.ReadCloser.Read(b)
17
+	if err != nil {
18
+		r.Once.Do(func() { r.ReadCloser.Close() })
19
+	}
20
+	return
21
+}
22
+
23
+func createStdInCloser(pipe io.WriteCloser, process hcsshim.Process) io.WriteCloser {
24
+	return ioutils.NewWriteCloserWrapper(pipe, func() error {
25
+		if err := pipe.Close(); err != nil {
26
+			return err
27
+		}
28
+
29
+		err := process.CloseStdin()
30
+		if err != nil && !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyClosed(err) {
31
+			// This error will occur if the compute system is currently shutting down
32
+			if perr, ok := err.(*hcsshim.ProcessError); ok && perr.Err != hcsshim.ErrVmcomputeOperationInvalidState {
33
+				return err
34
+			}
35
+		}
36
+
37
+		return nil
38
+	})
39
+}
40
+
41
+func (p *process) Cleanup() error {
42
+	return nil
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package local // import "github.com/docker/docker/libcontainerd/local"
1
+
2
+import (
3
+	"strings"
4
+
5
+	opengcs "github.com/Microsoft/opengcs/client"
6
+)
7
+
8
+// setupEnvironmentVariables converts a string array of environment variables
9
+// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
10
+func setupEnvironmentVariables(a []string) map[string]string {
11
+	r := make(map[string]string)
12
+	for _, s := range a {
13
+		arr := strings.SplitN(s, "=", 2)
14
+		if len(arr) == 2 {
15
+			r[arr[0]] = arr[1]
16
+		}
17
+	}
18
+	return r
19
+}
20
+
21
+// LCOWOption is a CreateOption required for LCOW configuration
22
+type LCOWOption struct {
23
+	Config *opengcs.Config
24
+}
25
+
26
+// Apply for the LCOW option is a no-op.
27
+func (s *LCOWOption) Apply(interface{}) error {
28
+	return nil
29
+}
30
+
31
+// debugGCS is a dirty hack for debugging for Linux Utility VMs. It simply
32
+// runs a bunch of commands inside the UVM, but seriously aides in advanced debugging.
33
+func (c *container) debugGCS() {
34
+	if c == nil || c.isWindows || c.hcsContainer == nil {
35
+		return
36
+	}
37
+	cfg := opengcs.Config{
38
+		Uvm:               c.hcsContainer,
39
+		UvmTimeoutSeconds: 600,
40
+	}
41
+	cfg.DebugGCS()
42
+}
0 43
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package local // import "github.com/docker/docker/libcontainerd/local"
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestEnvironmentParsing(t *testing.T) {
7
+	env := []string{"foo=bar", "car=hat", "a=b=c"}
8
+	result := setupEnvironmentVariables(env)
9
+	if len(result) != 3 || result["foo"] != "bar" || result["car"] != "hat" || result["a"] != "b=c" {
10
+		t.Fatalf("Expected map[foo:bar car:hat a:b=c], got %v", result)
11
+	}
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,1381 @@
0
+package local // import "github.com/docker/docker/libcontainerd/local"
1
+
2
+// This package contains the legacy in-proc calls in HCS using the v1 schema
3
+// for Windows runtime purposes.
4
+
5
+import (
6
+	"context"
7
+	"encoding/json"
8
+	"fmt"
9
+	"io/ioutil"
10
+	"os"
11
+	"path"
12
+	"path/filepath"
13
+	"regexp"
14
+	"strings"
15
+	"sync"
16
+	"syscall"
17
+	"time"
18
+
19
+	"github.com/Microsoft/hcsshim"
20
+	opengcs "github.com/Microsoft/opengcs/client"
21
+	"github.com/containerd/containerd"
22
+	"github.com/containerd/containerd/cio"
23
+
24
+	"github.com/docker/docker/errdefs"
25
+	"github.com/docker/docker/libcontainerd/queue"
26
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
27
+	"github.com/docker/docker/pkg/sysinfo"
28
+	"github.com/docker/docker/pkg/system"
29
+	specs "github.com/opencontainers/runtime-spec/specs-go"
30
+	"github.com/pkg/errors"
31
+	"github.com/sirupsen/logrus"
32
+	"golang.org/x/sys/windows"
33
+)
34
+
35
+type process struct {
36
+	id         string
37
+	pid        int
38
+	hcsProcess hcsshim.Process
39
+}
40
+
41
+type container struct {
42
+	sync.Mutex
43
+
44
+	// The ociSpec is required, as client.Create() needs a spec, but can
45
+	// be called from the RestartManager context which does not otherwise
46
+	// have access to the Spec
47
+	ociSpec *specs.Spec
48
+
49
+	isWindows    bool
50
+	hcsContainer hcsshim.Container
51
+
52
+	id               string
53
+	status           libcontainerdtypes.Status
54
+	exitedAt         time.Time
55
+	exitCode         uint32
56
+	waitCh           chan struct{}
57
+	init             *process
58
+	execs            map[string]*process
59
+	terminateInvoked bool
60
+}
61
+
62
+// Win32 error codes that are used for various workarounds
63
+// These really should be ALL_CAPS to match golangs syscall library and standard
64
+// Win32 error conventions, but golint insists on CamelCase.
65
+const (
66
+	CoEClassstring     = syscall.Errno(0x800401F3) // Invalid class string
67
+	ErrorNoNetwork     = syscall.Errno(1222)       // The network is not present or not started
68
+	ErrorBadPathname   = syscall.Errno(161)        // The specified path is invalid
69
+	ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
70
+)
71
+
72
+// defaultOwner is a tag passed to HCS to allow it to differentiate between
73
+// container creator management stacks. We hard code "docker" in the case
74
+// of docker.
75
+const defaultOwner = "docker"
76
+
77
+type client struct {
78
+	sync.Mutex
79
+
80
+	stateDir   string
81
+	backend    libcontainerdtypes.Backend
82
+	logger     *logrus.Entry
83
+	eventQ     queue.Queue
84
+	containers map[string]*container
85
+}
86
+
87
+// NewClient creates a new local executor for windows
88
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
89
+	c := &client{
90
+		stateDir:   stateDir,
91
+		backend:    b,
92
+		logger:     logrus.WithField("module", "libcontainerd").WithField("module", "libcontainerd").WithField("namespace", ns),
93
+		containers: make(map[string]*container),
94
+	}
95
+
96
+	return c, nil
97
+}
98
+
99
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
100
+	return containerd.Version{}, errors.New("not implemented on Windows")
101
+}
102
+
103
+// Create is the entrypoint to create a container from a spec.
104
+// Table below shows the fields required for HCS JSON calling parameters,
105
+// where if not populated, is omitted.
106
+// +-----------------+--------------------------------------------+---------------------------------------------------+
107
+// |                 | Isolation=Process                          | Isolation=Hyper-V                                 |
108
+// +-----------------+--------------------------------------------+---------------------------------------------------+
109
+// | VolumePath      | \\?\\Volume{GUIDa}                         |                                                   |
110
+// | LayerFolderPath | %root%\windowsfilter\containerID           |                                                   |
111
+// | Layers[]        | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID        |
112
+// | HvRuntime       |                                            | ImagePath=%root%\BaseLayerID\UtilityVM            |
113
+// +-----------------+--------------------------------------------+---------------------------------------------------+
114
+//
115
+// Isolation=Process example:
116
+//
117
+// {
118
+//	"SystemType": "Container",
119
+//	"Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
120
+//	"Owner": "docker",
121
+//	"VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
122
+//	"IgnoreFlushesDuringBoot": true,
123
+//	"LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
124
+//	"Layers": [{
125
+//		"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
126
+//		"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
127
+//	}],
128
+//	"HostName": "5e0055c814a6",
129
+//	"MappedDirectories": [],
130
+//	"HvPartition": false,
131
+//	"EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
132
+//}
133
+//
134
+// Isolation=Hyper-V example:
135
+//
136
+//{
137
+//	"SystemType": "Container",
138
+//	"Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
139
+//	"Owner": "docker",
140
+//	"IgnoreFlushesDuringBoot": true,
141
+//	"Layers": [{
142
+//		"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
143
+//		"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
144
+//	}],
145
+//	"HostName": "475c2c58933b",
146
+//	"MappedDirectories": [],
147
+//	"HvPartition": true,
148
+//	"EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
149
+//	"DNSSearchList": "a.com,b.com,c.com",
150
+//	"HvRuntime": {
151
+//		"ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
152
+//	},
153
+//}
154
+func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeOptions interface{}) error {
155
+	if ctr := c.getContainer(id); ctr != nil {
156
+		return errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
157
+	}
158
+
159
+	// spec.Linux must be nil for Windows containers, but spec.Windows
160
+	// will be filled in regardless of container platform.  This is a
161
+	// temporary workaround due to LCOW requiring layer folder paths,
162
+	// which are stored under spec.Windows.
163
+	//
164
+	// TODO: @darrenstahlmsft fix this once the OCI spec is updated to
165
+	// support layer folder paths for LCOW
166
+	if spec.Linux == nil {
167
+		return c.createWindows(id, spec, runtimeOptions)
168
+	}
169
+	return c.createLinux(id, spec, runtimeOptions)
170
+}
171
+
172
+func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
173
+	logger := c.logger.WithField("container", id)
174
+	configuration := &hcsshim.ContainerConfig{
175
+		SystemType:              "Container",
176
+		Name:                    id,
177
+		Owner:                   defaultOwner,
178
+		IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
179
+		HostName:                spec.Hostname,
180
+		HvPartition:             false,
181
+	}
182
+
183
+	c.extractResourcesFromSpec(spec, configuration)
184
+
185
+	if spec.Windows.Resources != nil {
186
+		if spec.Windows.Resources.Storage != nil {
187
+			if spec.Windows.Resources.Storage.Bps != nil {
188
+				configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
189
+			}
190
+			if spec.Windows.Resources.Storage.Iops != nil {
191
+				configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
192
+			}
193
+		}
194
+	}
195
+
196
+	if spec.Windows.HyperV != nil {
197
+		configuration.HvPartition = true
198
+	}
199
+
200
+	if spec.Windows.Network != nil {
201
+		configuration.EndpointList = spec.Windows.Network.EndpointList
202
+		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
203
+		if spec.Windows.Network.DNSSearchList != nil {
204
+			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
205
+		}
206
+		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
207
+	}
208
+
209
+	if cs, ok := spec.Windows.CredentialSpec.(string); ok {
210
+		configuration.Credentials = cs
211
+	}
212
+
213
+	// We must have least two layers in the spec, the bottom one being a
214
+	// base image, the top one being the RW layer.
215
+	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
216
+		return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
217
+	}
218
+
219
+	// Strip off the top-most layer as that's passed in separately to HCS
220
+	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
221
+	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
222
+
223
+	if configuration.HvPartition {
224
+		// We don't currently support setting the utility VM image explicitly.
225
+		// TODO @swernli/jhowardmsft circa RS5, this may be re-locatable.
226
+		if spec.Windows.HyperV.UtilityVMPath != "" {
227
+			return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
228
+		}
229
+
230
+		// Find the upper-most utility VM image.
231
+		var uvmImagePath string
232
+		for _, path := range layerFolders {
233
+			fullPath := filepath.Join(path, "UtilityVM")
234
+			_, err := os.Stat(fullPath)
235
+			if err == nil {
236
+				uvmImagePath = fullPath
237
+				break
238
+			}
239
+			if !os.IsNotExist(err) {
240
+				return err
241
+			}
242
+		}
243
+		if uvmImagePath == "" {
244
+			return errors.New("utility VM image could not be found")
245
+		}
246
+		configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
247
+
248
+		if spec.Root.Path != "" {
249
+			return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
250
+		}
251
+	} else {
252
+		const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
253
+		if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
254
+			return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
255
+		}
256
+		// HCS API requires the trailing backslash to be removed
257
+		configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
258
+	}
259
+
260
+	if spec.Root.Readonly {
261
+		return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
262
+	}
263
+
264
+	for _, layerPath := range layerFolders {
265
+		_, filename := filepath.Split(layerPath)
266
+		g, err := hcsshim.NameToGuid(filename)
267
+		if err != nil {
268
+			return err
269
+		}
270
+		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
271
+			ID:   g.ToString(),
272
+			Path: layerPath,
273
+		})
274
+	}
275
+
276
+	// Add the mounts (volumes, bind mounts etc) to the structure
277
+	var mds []hcsshim.MappedDir
278
+	var mps []hcsshim.MappedPipe
279
+	for _, mount := range spec.Mounts {
280
+		const pipePrefix = `\\.\pipe\`
281
+		if mount.Type != "" {
282
+			return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
283
+		}
284
+		if strings.HasPrefix(mount.Destination, pipePrefix) {
285
+			mp := hcsshim.MappedPipe{
286
+				HostPath:          mount.Source,
287
+				ContainerPipeName: mount.Destination[len(pipePrefix):],
288
+			}
289
+			mps = append(mps, mp)
290
+		} else {
291
+			md := hcsshim.MappedDir{
292
+				HostPath:      mount.Source,
293
+				ContainerPath: mount.Destination,
294
+				ReadOnly:      false,
295
+			}
296
+			for _, o := range mount.Options {
297
+				if strings.ToLower(o) == "ro" {
298
+					md.ReadOnly = true
299
+				}
300
+			}
301
+			mds = append(mds, md)
302
+		}
303
+	}
304
+	configuration.MappedDirectories = mds
305
+	if len(mps) > 0 && system.GetOSVersion().Build < 16299 { // RS3
306
+		return errors.New("named pipe mounts are not supported on this version of Windows")
307
+	}
308
+	configuration.MappedPipes = mps
309
+
310
+	if len(spec.Windows.Devices) > 0 {
311
+		// Add any device assignments
312
+		if configuration.HvPartition {
313
+			return errors.New("device assignment is not supported for HyperV containers")
314
+		}
315
+		if system.GetOSVersion().Build < 17763 { // RS5
316
+			return errors.New("device assignment requires Windows builds RS5 (17763+) or later")
317
+		}
318
+		for _, d := range spec.Windows.Devices {
319
+			configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
320
+		}
321
+	}
322
+
323
+	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
324
+	if err != nil {
325
+		return err
326
+	}
327
+
328
+	// Construct a container object for calling start on it.
329
+	ctr := &container{
330
+		id:           id,
331
+		execs:        make(map[string]*process),
332
+		isWindows:    true,
333
+		ociSpec:      spec,
334
+		hcsContainer: hcsContainer,
335
+		status:       libcontainerdtypes.StatusCreated,
336
+		waitCh:       make(chan struct{}),
337
+	}
338
+
339
+	logger.Debug("starting container")
340
+	if err = hcsContainer.Start(); err != nil {
341
+		c.logger.WithError(err).Error("failed to start container")
342
+		ctr.Lock()
343
+		if err := c.terminateContainer(ctr); err != nil {
344
+			c.logger.WithError(err).Error("failed to cleanup after a failed Start")
345
+		} else {
346
+			c.logger.Debug("cleaned up after failed Start by calling Terminate")
347
+		}
348
+		ctr.Unlock()
349
+		return err
350
+	}
351
+
352
+	c.Lock()
353
+	c.containers[id] = ctr
354
+	c.Unlock()
355
+
356
+	logger.Debug("createWindows() completed successfully")
357
+	return nil
358
+
359
+}
360
+
361
+func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interface{}) error {
362
+	logrus.Debugf("libcontainerd: createLinux(): containerId %s ", id)
363
+	logger := c.logger.WithField("container", id)
364
+
365
+	if runtimeOptions == nil {
366
+		return fmt.Errorf("lcow option must be supplied to the runtime")
367
+	}
368
+	lcowConfig, ok := runtimeOptions.(*opengcs.Config)
369
+	if !ok {
370
+		return fmt.Errorf("lcow option must be supplied to the runtime")
371
+	}
372
+
373
+	configuration := &hcsshim.ContainerConfig{
374
+		HvPartition:                 true,
375
+		Name:                        id,
376
+		SystemType:                  "container",
377
+		ContainerType:               "linux",
378
+		Owner:                       defaultOwner,
379
+		TerminateOnLastHandleClosed: true,
380
+	}
381
+
382
+	if lcowConfig.ActualMode == opengcs.ModeActualVhdx {
383
+		configuration.HvRuntime = &hcsshim.HvRuntime{
384
+			ImagePath:          lcowConfig.Vhdx,
385
+			BootSource:         "Vhd",
386
+			WritableBootSource: false,
387
+		}
388
+	} else {
389
+		configuration.HvRuntime = &hcsshim.HvRuntime{
390
+			ImagePath:           lcowConfig.KirdPath,
391
+			LinuxKernelFile:     lcowConfig.KernelFile,
392
+			LinuxInitrdFile:     lcowConfig.InitrdFile,
393
+			LinuxBootParameters: lcowConfig.BootParameters,
394
+		}
395
+	}
396
+
397
+	if spec.Windows == nil {
398
+		return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
399
+	}
400
+
401
+	c.extractResourcesFromSpec(spec, configuration)
402
+
403
+	// We must have least one layer in the spec
404
+	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
405
+		return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
406
+	}
407
+
408
+	// Strip off the top-most layer as that's passed in separately to HCS
409
+	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
410
+	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
411
+
412
+	for _, layerPath := range layerFolders {
413
+		_, filename := filepath.Split(layerPath)
414
+		g, err := hcsshim.NameToGuid(filename)
415
+		if err != nil {
416
+			return err
417
+		}
418
+		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
419
+			ID:   g.ToString(),
420
+			Path: filepath.Join(layerPath, "layer.vhd"),
421
+		})
422
+	}
423
+
424
+	if spec.Windows.Network != nil {
425
+		configuration.EndpointList = spec.Windows.Network.EndpointList
426
+		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
427
+		if spec.Windows.Network.DNSSearchList != nil {
428
+			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
429
+		}
430
+		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
431
+	}
432
+
433
+	// Add the mounts (volumes, bind mounts etc) to the structure. We have to do
434
+	// some translation for both the mapped directories passed into HCS and in
435
+	// the spec.
436
+	//
437
+	// For HCS, we only pass in the mounts from the spec which are type "bind".
438
+	// Further, the "ContainerPath" field (which is a little mis-leadingly
439
+	// named when it applies to the utility VM rather than the container in the
440
+	// utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed
441
+	// by the caller through a 'uvmpath' option.
442
+	//
443
+	// We do similar translation for the mounts in the spec by stripping out
444
+	// the uvmpath option, and translating the Source path to the location in the
445
+	// utility VM calculated above.
446
+	//
447
+	// From inside the utility VM, you would see a 9p mount such as in the following
448
+	// where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds
449
+	// specifically:
450
+	//
451
+	//	/ # mount
452
+	//	rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934)
453
+	//	proc on /proc type proc (rw,relatime)
454
+	//	sysfs on /sys type sysfs (rw,relatime)
455
+	//	udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755)
456
+	//	tmpfs on /run type tmpfs (rw,relatime)
457
+	//	cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma)
458
+	//	mqueue on /dev/mqueue type mqueue (rw,relatime)
459
+	//	devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
460
+	//	/binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6)
461
+	//	/dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
462
+	//	/dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
463
+	//	overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work)
464
+	//
465
+	//  /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l
466
+	//	total 16
467
+	//	drwx------    3 0        0               60 Sep  7 18:54 binds
468
+	//	-rw-r--r--    1 0        0             3345 Sep  7 18:54 config.json
469
+	//	drwxr-xr-x   10 0        0             4096 Sep  6 17:26 layer0
470
+	//	drwxr-xr-x    1 0        0             4096 Sep  7 18:54 rootfs
471
+	//	drwxr-xr-x    5 0        0             4096 Sep  7 18:54 scratch
472
+	//
473
+	//	/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds
474
+	//	total 0
475
+	//	drwxrwxrwt    2 0        0             4096 Sep  7 16:51 target
476
+
477
+	mds := []hcsshim.MappedDir{}
478
+	specMounts := []specs.Mount{}
479
+	for _, mount := range spec.Mounts {
480
+		specMount := mount
481
+		if mount.Type == "bind" {
482
+			// Strip out the uvmpath from the options
483
+			updatedOptions := []string{}
484
+			uvmPath := ""
485
+			readonly := false
486
+			for _, opt := range mount.Options {
487
+				dropOption := false
488
+				elements := strings.SplitN(opt, "=", 2)
489
+				switch elements[0] {
490
+				case "uvmpath":
491
+					uvmPath = elements[1]
492
+					dropOption = true
493
+				case "rw":
494
+				case "ro":
495
+					readonly = true
496
+				case "rbind":
497
+				default:
498
+					return fmt.Errorf("unsupported option %q", opt)
499
+				}
500
+				if !dropOption {
501
+					updatedOptions = append(updatedOptions, opt)
502
+				}
503
+			}
504
+			mount.Options = updatedOptions
505
+			if uvmPath == "" {
506
+				return fmt.Errorf("no uvmpath for bind mount %+v", mount)
507
+			}
508
+			md := hcsshim.MappedDir{
509
+				HostPath:          mount.Source,
510
+				ContainerPath:     path.Join(uvmPath, mount.Destination),
511
+				CreateInUtilityVM: true,
512
+				ReadOnly:          readonly,
513
+			}
514
+			// If we are 1803/RS4+ enable LinuxMetadata support by default
515
+			if system.GetOSVersion().Build >= 17134 {
516
+				md.LinuxMetadata = true
517
+			}
518
+			mds = append(mds, md)
519
+			specMount.Source = path.Join(uvmPath, mount.Destination)
520
+		}
521
+		specMounts = append(specMounts, specMount)
522
+	}
523
+	configuration.MappedDirectories = mds
524
+
525
+	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
526
+	if err != nil {
527
+		return err
528
+	}
529
+
530
+	spec.Mounts = specMounts
531
+
532
+	// Construct a container object for calling start on it.
533
+	ctr := &container{
534
+		id:           id,
535
+		execs:        make(map[string]*process),
536
+		isWindows:    false,
537
+		ociSpec:      spec,
538
+		hcsContainer: hcsContainer,
539
+		status:       libcontainerdtypes.StatusCreated,
540
+		waitCh:       make(chan struct{}),
541
+	}
542
+
543
+	// Start the container.
544
+	logger.Debug("starting container")
545
+	if err = hcsContainer.Start(); err != nil {
546
+		c.logger.WithError(err).Error("failed to start container")
547
+		ctr.debugGCS()
548
+		ctr.Lock()
549
+		if err := c.terminateContainer(ctr); err != nil {
550
+			c.logger.WithError(err).Error("failed to cleanup after a failed Start")
551
+		} else {
552
+			c.logger.Debug("cleaned up after failed Start by calling Terminate")
553
+		}
554
+		ctr.Unlock()
555
+		return err
556
+	}
557
+	ctr.debugGCS()
558
+
559
+	c.Lock()
560
+	c.containers[id] = ctr
561
+	c.Unlock()
562
+
563
+	c.eventQ.Append(id, func() {
564
+		ei := libcontainerdtypes.EventInfo{
565
+			ContainerID: id,
566
+		}
567
+		c.logger.WithFields(logrus.Fields{
568
+			"container": ctr.id,
569
+			"event":     libcontainerdtypes.EventCreate,
570
+		}).Info("sending event")
571
+		err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, ei)
572
+		if err != nil {
573
+			c.logger.WithError(err).WithFields(logrus.Fields{
574
+				"container": id,
575
+				"event":     libcontainerdtypes.EventCreate,
576
+			}).Error("failed to process event")
577
+		}
578
+	})
579
+
580
+	logger.Debug("createLinux() completed successfully")
581
+	return nil
582
+}
583
+
584
+func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
585
+	if spec.Windows.Resources != nil {
586
+		if spec.Windows.Resources.CPU != nil {
587
+			if spec.Windows.Resources.CPU.Count != nil {
588
+				// This check is being done here rather than in adaptContainerSettings
589
+				// because we don't want to update the HostConfig in case this container
590
+				// is moved to a host with more CPUs than this one.
591
+				cpuCount := *spec.Windows.Resources.CPU.Count
592
+				hostCPUCount := uint64(sysinfo.NumCPU())
593
+				if cpuCount > hostCPUCount {
594
+					c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
595
+					cpuCount = hostCPUCount
596
+				}
597
+				configuration.ProcessorCount = uint32(cpuCount)
598
+			}
599
+			if spec.Windows.Resources.CPU.Shares != nil {
600
+				configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
601
+			}
602
+			if spec.Windows.Resources.CPU.Maximum != nil {
603
+				configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum)
604
+			}
605
+		}
606
+		if spec.Windows.Resources.Memory != nil {
607
+			if spec.Windows.Resources.Memory.Limit != nil {
608
+				configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
609
+			}
610
+		}
611
+	}
612
+}
613
+
614
+func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
615
+	ctr := c.getContainer(id)
616
+	switch {
617
+	case ctr == nil:
618
+		return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
619
+	case ctr.init != nil:
620
+		return -1, errors.WithStack(errdefs.Conflict(errors.New("container already started")))
621
+	}
622
+
623
+	logger := c.logger.WithField("container", id)
624
+
625
+	// Note we always tell HCS to create stdout as it's required
626
+	// regardless of '-i' or '-t' options, so that docker can always grab
627
+	// the output through logs. We also tell HCS to always create stdin,
628
+	// even if it's not used - it will be closed shortly. Stderr is only
629
+	// created if it we're not -t.
630
+	var (
631
+		emulateConsole   bool
632
+		createStdErrPipe bool
633
+	)
634
+	if ctr.ociSpec.Process != nil {
635
+		emulateConsole = ctr.ociSpec.Process.Terminal
636
+		createStdErrPipe = !ctr.ociSpec.Process.Terminal
637
+	}
638
+
639
+	createProcessParms := &hcsshim.ProcessConfig{
640
+		EmulateConsole:   emulateConsole,
641
+		WorkingDirectory: ctr.ociSpec.Process.Cwd,
642
+		CreateStdInPipe:  true,
643
+		CreateStdOutPipe: true,
644
+		CreateStdErrPipe: createStdErrPipe,
645
+	}
646
+
647
+	if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
648
+		createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
649
+		createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
650
+	}
651
+
652
+	// Configure the environment for the process
653
+	createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
654
+	if ctr.isWindows {
655
+		createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
656
+	} else {
657
+		createProcessParms.CommandArgs = ctr.ociSpec.Process.Args
658
+	}
659
+	createProcessParms.User = ctr.ociSpec.Process.User.Username
660
+
661
+	// LCOW requires the raw OCI spec passed through HCS and onwards to
662
+	// GCS for the utility VM.
663
+	if !ctr.isWindows {
664
+		ociBuf, err := json.Marshal(ctr.ociSpec)
665
+		if err != nil {
666
+			return -1, err
667
+		}
668
+		ociRaw := json.RawMessage(ociBuf)
669
+		createProcessParms.OCISpecification = &ociRaw
670
+	}
671
+
672
+	ctr.Lock()
673
+	defer ctr.Unlock()
674
+
675
+	// Start the command running in the container.
676
+	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
677
+	if err != nil {
678
+		logger.WithError(err).Error("CreateProcess() failed")
679
+		return -1, err
680
+	}
681
+	defer func() {
682
+		if err != nil {
683
+			if err := newProcess.Kill(); err != nil {
684
+				logger.WithError(err).Error("failed to kill process")
685
+			}
686
+			go func() {
687
+				if err := newProcess.Wait(); err != nil {
688
+					logger.WithError(err).Error("failed to wait for process")
689
+				}
690
+				if err := newProcess.Close(); err != nil {
691
+					logger.WithError(err).Error("failed to clean process resources")
692
+				}
693
+			}()
694
+		}
695
+	}()
696
+	p := &process{
697
+		hcsProcess: newProcess,
698
+		id:         libcontainerdtypes.InitProcessName,
699
+		pid:        newProcess.Pid(),
700
+	}
701
+	logger.WithField("pid", p.pid).Debug("init process started")
702
+
703
+	dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal)
704
+	if err != nil {
705
+		logger.WithError(err).Error("failed to get stdio pipes")
706
+		return -1, err
707
+	}
708
+	_, err = attachStdio(dio)
709
+	if err != nil {
710
+		logger.WithError(err).Error("failed to attache stdio")
711
+		return -1, err
712
+	}
713
+	ctr.status = libcontainerdtypes.StatusRunning
714
+	ctr.init = p
715
+
716
+	// Spin up a go routine waiting for exit to handle cleanup
717
+	go c.reapProcess(ctr, p)
718
+
719
+	// Generate the associated event
720
+	c.eventQ.Append(id, func() {
721
+		ei := libcontainerdtypes.EventInfo{
722
+			ContainerID: id,
723
+			ProcessID:   libcontainerdtypes.InitProcessName,
724
+			Pid:         uint32(p.pid),
725
+		}
726
+		c.logger.WithFields(logrus.Fields{
727
+			"container":  ctr.id,
728
+			"event":      libcontainerdtypes.EventStart,
729
+			"event-info": ei,
730
+		}).Info("sending event")
731
+		err := c.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei)
732
+		if err != nil {
733
+			c.logger.WithError(err).WithFields(logrus.Fields{
734
+				"container":  id,
735
+				"event":      libcontainerdtypes.EventStart,
736
+				"event-info": ei,
737
+			}).Error("failed to process event")
738
+		}
739
+	})
740
+	logger.Debug("start() completed")
741
+	return p.pid, nil
742
+}
743
+
744
+func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
745
+	stdin, stdout, stderr, err := newProcess.Stdio()
746
+	if err != nil {
747
+		return nil, err
748
+	}
749
+
750
+	dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal)
751
+
752
+	// Convert io.ReadClosers to io.Readers
753
+	if stdout != nil {
754
+		dio.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
755
+	}
756
+	if stderr != nil {
757
+		dio.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
758
+	}
759
+	return dio, nil
760
+}
761
+
762
+// Exec adds a process in an running container
763
+func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
764
+	ctr := c.getContainer(containerID)
765
+	switch {
766
+	case ctr == nil:
767
+		return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
768
+	case ctr.hcsContainer == nil:
769
+		return -1, errors.WithStack(errdefs.InvalidParameter(errors.New("container is not running")))
770
+	case ctr.execs != nil && ctr.execs[processID] != nil:
771
+		return -1, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
772
+	}
773
+	logger := c.logger.WithFields(logrus.Fields{
774
+		"container": containerID,
775
+		"exec":      processID,
776
+	})
777
+
778
+	// Note we always tell HCS to
779
+	// create stdout as it's required regardless of '-i' or '-t' options, so that
780
+	// docker can always grab the output through logs. We also tell HCS to always
781
+	// create stdin, even if it's not used - it will be closed shortly. Stderr
782
+	// is only created if it we're not -t.
783
+	createProcessParms := hcsshim.ProcessConfig{
784
+		CreateStdInPipe:  true,
785
+		CreateStdOutPipe: true,
786
+		CreateStdErrPipe: !spec.Terminal,
787
+	}
788
+	if spec.Terminal {
789
+		createProcessParms.EmulateConsole = true
790
+		if spec.ConsoleSize != nil {
791
+			createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height)
792
+			createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width)
793
+		}
794
+	}
795
+
796
+	// Take working directory from the process to add if it is defined,
797
+	// otherwise take from the first process.
798
+	if spec.Cwd != "" {
799
+		createProcessParms.WorkingDirectory = spec.Cwd
800
+	} else {
801
+		createProcessParms.WorkingDirectory = ctr.ociSpec.Process.Cwd
802
+	}
803
+
804
+	// Configure the environment for the process
805
+	createProcessParms.Environment = setupEnvironmentVariables(spec.Env)
806
+	if ctr.isWindows {
807
+		createProcessParms.CommandLine = strings.Join(spec.Args, " ")
808
+	} else {
809
+		createProcessParms.CommandArgs = spec.Args
810
+	}
811
+	createProcessParms.User = spec.User.Username
812
+
813
+	logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
814
+
815
+	// Start the command running in the container.
816
+	newProcess, err := ctr.hcsContainer.CreateProcess(&createProcessParms)
817
+	if err != nil {
818
+		logger.WithError(err).Errorf("exec's CreateProcess() failed")
819
+		return -1, err
820
+	}
821
+	pid := newProcess.Pid()
822
+	defer func() {
823
+		if err != nil {
824
+			if err := newProcess.Kill(); err != nil {
825
+				logger.WithError(err).Error("failed to kill process")
826
+			}
827
+			go func() {
828
+				if err := newProcess.Wait(); err != nil {
829
+					logger.WithError(err).Error("failed to wait for process")
830
+				}
831
+				if err := newProcess.Close(); err != nil {
832
+					logger.WithError(err).Error("failed to clean process resources")
833
+				}
834
+			}()
835
+		}
836
+	}()
837
+
838
+	dio, err := newIOFromProcess(newProcess, spec.Terminal)
839
+	if err != nil {
840
+		logger.WithError(err).Error("failed to get stdio pipes")
841
+		return -1, err
842
+	}
843
+	// Tell the engine to attach streams back to the client
844
+	_, err = attachStdio(dio)
845
+	if err != nil {
846
+		return -1, err
847
+	}
848
+
849
+	p := &process{
850
+		id:         processID,
851
+		pid:        pid,
852
+		hcsProcess: newProcess,
853
+	}
854
+
855
+	// Add the process to the container's list of processes
856
+	ctr.Lock()
857
+	ctr.execs[processID] = p
858
+	ctr.Unlock()
859
+
860
+	// Spin up a go routine waiting for exit to handle cleanup
861
+	go c.reapProcess(ctr, p)
862
+
863
+	c.eventQ.Append(ctr.id, func() {
864
+		ei := libcontainerdtypes.EventInfo{
865
+			ContainerID: ctr.id,
866
+			ProcessID:   p.id,
867
+			Pid:         uint32(p.pid),
868
+		}
869
+		c.logger.WithFields(logrus.Fields{
870
+			"container":  ctr.id,
871
+			"event":      libcontainerdtypes.EventExecAdded,
872
+			"event-info": ei,
873
+		}).Info("sending event")
874
+		err := c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExecAdded, ei)
875
+		if err != nil {
876
+			c.logger.WithError(err).WithFields(logrus.Fields{
877
+				"container":  ctr.id,
878
+				"event":      libcontainerdtypes.EventExecAdded,
879
+				"event-info": ei,
880
+			}).Error("failed to process event")
881
+		}
882
+		err = c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExecStarted, ei)
883
+		if err != nil {
884
+			c.logger.WithError(err).WithFields(logrus.Fields{
885
+				"container":  ctr.id,
886
+				"event":      libcontainerdtypes.EventExecStarted,
887
+				"event-info": ei,
888
+			}).Error("failed to process event")
889
+		}
890
+	})
891
+
892
+	return pid, nil
893
+}
894
+
895
+// Signal handles `docker stop` on Windows. While Linux has support for
896
+// the full range of signals, signals aren't really implemented on Windows.
897
+// We fake supporting regular stop and -9 to force kill.
898
+func (c *client) SignalProcess(_ context.Context, containerID, processID string, signal int) error {
899
+	ctr, p, err := c.getProcess(containerID, processID)
900
+	if err != nil {
901
+		return err
902
+	}
903
+
904
+	logger := c.logger.WithFields(logrus.Fields{
905
+		"container": containerID,
906
+		"process":   processID,
907
+		"pid":       p.pid,
908
+		"signal":    signal,
909
+	})
910
+	logger.Debug("Signal()")
911
+
912
+	if processID == libcontainerdtypes.InitProcessName {
913
+		if syscall.Signal(signal) == syscall.SIGKILL {
914
+			// Terminate the compute system
915
+			ctr.Lock()
916
+			ctr.terminateInvoked = true
917
+			if err := ctr.hcsContainer.Terminate(); err != nil {
918
+				if !hcsshim.IsPending(err) {
919
+					logger.WithError(err).Error("failed to terminate hccshim container")
920
+				}
921
+			}
922
+			ctr.Unlock()
923
+		} else {
924
+			// Shut down the container
925
+			if err := ctr.hcsContainer.Shutdown(); err != nil {
926
+				if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
927
+					// ignore errors
928
+					logger.WithError(err).Error("failed to shutdown hccshim container")
929
+				}
930
+			}
931
+		}
932
+	} else {
933
+		return p.hcsProcess.Kill()
934
+	}
935
+
936
+	return nil
937
+}
938
+
939
+// Resize handles a CLI event to resize an interactive docker run or docker
940
+// exec window.
941
+func (c *client) ResizeTerminal(_ context.Context, containerID, processID string, width, height int) error {
942
+	_, p, err := c.getProcess(containerID, processID)
943
+	if err != nil {
944
+		return err
945
+	}
946
+
947
+	c.logger.WithFields(logrus.Fields{
948
+		"container": containerID,
949
+		"process":   processID,
950
+		"height":    height,
951
+		"width":     width,
952
+		"pid":       p.pid,
953
+	}).Debug("resizing")
954
+	return p.hcsProcess.ResizeConsole(uint16(width), uint16(height))
955
+}
956
+
957
+func (c *client) CloseStdin(_ context.Context, containerID, processID string) error {
958
+	_, p, err := c.getProcess(containerID, processID)
959
+	if err != nil {
960
+		return err
961
+	}
962
+
963
+	return p.hcsProcess.CloseStdin()
964
+}
965
+
966
+// Pause handles pause requests for containers
967
+func (c *client) Pause(_ context.Context, containerID string) error {
968
+	ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
969
+	if err != nil {
970
+		return err
971
+	}
972
+
973
+	if ctr.ociSpec.Windows.HyperV == nil {
974
+		return errors.New("cannot pause Windows Server Containers")
975
+	}
976
+
977
+	ctr.Lock()
978
+	defer ctr.Unlock()
979
+
980
+	if err = ctr.hcsContainer.Pause(); err != nil {
981
+		return err
982
+	}
983
+
984
+	ctr.status = libcontainerdtypes.StatusPaused
985
+
986
+	c.eventQ.Append(containerID, func() {
987
+		err := c.backend.ProcessEvent(containerID, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
988
+			ContainerID: containerID,
989
+			ProcessID:   libcontainerdtypes.InitProcessName,
990
+		})
991
+		c.logger.WithFields(logrus.Fields{
992
+			"container": ctr.id,
993
+			"event":     libcontainerdtypes.EventPaused,
994
+		}).Info("sending event")
995
+		if err != nil {
996
+			c.logger.WithError(err).WithFields(logrus.Fields{
997
+				"container": containerID,
998
+				"event":     libcontainerdtypes.EventPaused,
999
+			}).Error("failed to process event")
1000
+		}
1001
+	})
1002
+
1003
+	return nil
1004
+}
1005
+
1006
+// Resume handles resume requests for containers
1007
+func (c *client) Resume(_ context.Context, containerID string) error {
1008
+	ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
1009
+	if err != nil {
1010
+		return err
1011
+	}
1012
+
1013
+	if ctr.ociSpec.Windows.HyperV == nil {
1014
+		return errors.New("cannot resume Windows Server Containers")
1015
+	}
1016
+
1017
+	ctr.Lock()
1018
+	defer ctr.Unlock()
1019
+
1020
+	if err = ctr.hcsContainer.Resume(); err != nil {
1021
+		return err
1022
+	}
1023
+
1024
+	ctr.status = libcontainerdtypes.StatusRunning
1025
+
1026
+	c.eventQ.Append(containerID, func() {
1027
+		err := c.backend.ProcessEvent(containerID, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
1028
+			ContainerID: containerID,
1029
+			ProcessID:   libcontainerdtypes.InitProcessName,
1030
+		})
1031
+		c.logger.WithFields(logrus.Fields{
1032
+			"container": ctr.id,
1033
+			"event":     libcontainerdtypes.EventResumed,
1034
+		}).Info("sending event")
1035
+		if err != nil {
1036
+			c.logger.WithError(err).WithFields(logrus.Fields{
1037
+				"container": containerID,
1038
+				"event":     libcontainerdtypes.EventResumed,
1039
+			}).Error("failed to process event")
1040
+		}
1041
+	})
1042
+
1043
+	return nil
1044
+}
1045
+
1046
+// Stats handles stats requests for containers
1047
+func (c *client) Stats(_ context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
1048
+	ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
1049
+	if err != nil {
1050
+		return nil, err
1051
+	}
1052
+
1053
+	readAt := time.Now()
1054
+	s, err := ctr.hcsContainer.Statistics()
1055
+	if err != nil {
1056
+		return nil, err
1057
+	}
1058
+	return &libcontainerdtypes.Stats{
1059
+		Read:     readAt,
1060
+		HCSStats: &s,
1061
+	}, nil
1062
+}
1063
+
1064
+// Restore is the handler for restoring a container
1065
+func (c *client) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (bool, int, error) {
1066
+	c.logger.WithField("container", id).Debug("restore()")
1067
+
1068
+	// TODO Windows: On RS1, a re-attach isn't possible.
1069
+	// However, there is a scenario in which there is an issue.
1070
+	// Consider a background container. The daemon dies unexpectedly.
1071
+	// HCS will still have the compute service alive and running.
1072
+	// For consistence, we call in to shoot it regardless if HCS knows about it
1073
+	// We explicitly just log a warning if the terminate fails.
1074
+	// Then we tell the backend the container exited.
1075
+	if hc, err := hcsshim.OpenContainer(id); err == nil {
1076
+		const terminateTimeout = time.Minute * 2
1077
+		err := hc.Terminate()
1078
+
1079
+		if hcsshim.IsPending(err) {
1080
+			err = hc.WaitTimeout(terminateTimeout)
1081
+		} else if hcsshim.IsAlreadyStopped(err) {
1082
+			err = nil
1083
+		}
1084
+
1085
+		if err != nil {
1086
+			c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
1087
+			return false, -1, err
1088
+		}
1089
+	}
1090
+	return false, -1, nil
1091
+}
1092
+
1093
+// GetPidsForContainer returns a list of process IDs running in a container.
1094
+// Not used on Windows.
1095
+func (c *client) ListPids(_ context.Context, _ string) ([]uint32, error) {
1096
+	return nil, errors.New("not implemented on Windows")
1097
+}
1098
+
1099
+// Summary returns a summary of the processes running in a container.
1100
+// This is present in Windows to support docker top. In linux, the
1101
+// engine shells out to ps to get process information. On Windows, as
1102
+// the containers could be Hyper-V containers, they would not be
1103
+// visible on the container host. However, libcontainerd does have
1104
+// that information.
1105
+func (c *client) Summary(_ context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
1106
+	ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
1107
+	if err != nil {
1108
+		return nil, err
1109
+	}
1110
+
1111
+	p, err := ctr.hcsContainer.ProcessList()
1112
+	if err != nil {
1113
+		return nil, err
1114
+	}
1115
+
1116
+	pl := make([]libcontainerdtypes.Summary, len(p))
1117
+	for i := range p {
1118
+		pl[i] = libcontainerdtypes.Summary(p[i])
1119
+	}
1120
+	return pl, nil
1121
+}
1122
+
1123
+func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
1124
+	ec := -1
1125
+	ctr := c.getContainer(containerID)
1126
+	if ctr == nil {
1127
+		return uint32(ec), time.Now(), errors.WithStack(errdefs.NotFound(errors.New("no such container")))
1128
+	}
1129
+
1130
+	select {
1131
+	case <-ctx.Done():
1132
+		return uint32(ec), time.Now(), errors.WithStack(ctx.Err())
1133
+	case <-ctr.waitCh:
1134
+	default:
1135
+		return uint32(ec), time.Now(), errors.New("container is not stopped")
1136
+	}
1137
+
1138
+	ctr.Lock()
1139
+	defer ctr.Unlock()
1140
+	return ctr.exitCode, ctr.exitedAt, nil
1141
+}
1142
+
1143
+func (c *client) Delete(_ context.Context, containerID string) error {
1144
+	c.Lock()
1145
+	defer c.Unlock()
1146
+	ctr := c.containers[containerID]
1147
+	if ctr == nil {
1148
+		return errors.WithStack(errdefs.NotFound(errors.New("no such container")))
1149
+	}
1150
+
1151
+	ctr.Lock()
1152
+	defer ctr.Unlock()
1153
+
1154
+	switch ctr.status {
1155
+	case libcontainerdtypes.StatusCreated:
1156
+		if err := c.shutdownContainer(ctr); err != nil {
1157
+			return err
1158
+		}
1159
+		fallthrough
1160
+	case libcontainerdtypes.StatusStopped:
1161
+		delete(c.containers, containerID)
1162
+		return nil
1163
+	}
1164
+
1165
+	return errors.WithStack(errdefs.InvalidParameter(errors.New("container is not stopped")))
1166
+}
1167
+
1168
+func (c *client) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
1169
+	c.Lock()
1170
+	defer c.Unlock()
1171
+	ctr := c.containers[containerID]
1172
+	if ctr == nil {
1173
+		return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
1174
+	}
1175
+
1176
+	ctr.Lock()
1177
+	defer ctr.Unlock()
1178
+	return ctr.status, nil
1179
+}
1180
+
1181
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
1182
+	// Updating resource isn't supported on Windows
1183
+	// but we should return nil for enabling updating container
1184
+	return nil
1185
+}
1186
+
1187
+func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
1188
+	return errors.New("Windows: Containers do not support checkpoints")
1189
+}
1190
+
1191
+func (c *client) getContainer(id string) *container {
1192
+	c.Lock()
1193
+	ctr := c.containers[id]
1194
+	c.Unlock()
1195
+
1196
+	return ctr
1197
+}
1198
+
1199
+func (c *client) getProcess(containerID, processID string) (*container, *process, error) {
1200
+	ctr := c.getContainer(containerID)
1201
+	switch {
1202
+	case ctr == nil:
1203
+		return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
1204
+	case ctr.init == nil:
1205
+		return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("container is not running")))
1206
+	case processID == libcontainerdtypes.InitProcessName:
1207
+		return ctr, ctr.init, nil
1208
+	default:
1209
+		ctr.Lock()
1210
+		defer ctr.Unlock()
1211
+		if ctr.execs == nil {
1212
+			return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no execs")))
1213
+		}
1214
+	}
1215
+
1216
+	p := ctr.execs[processID]
1217
+	if p == nil {
1218
+		return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no such exec")))
1219
+	}
1220
+
1221
+	return ctr, p, nil
1222
+}
1223
+
1224
+// ctr mutex must be held when calling this function.
1225
+func (c *client) shutdownContainer(ctr *container) error {
1226
+	var err error
1227
+	const waitTimeout = time.Minute * 5
1228
+
1229
+	if !ctr.terminateInvoked {
1230
+		err = ctr.hcsContainer.Shutdown()
1231
+	}
1232
+
1233
+	if hcsshim.IsPending(err) || ctr.terminateInvoked {
1234
+		err = ctr.hcsContainer.WaitTimeout(waitTimeout)
1235
+	} else if hcsshim.IsAlreadyStopped(err) {
1236
+		err = nil
1237
+	}
1238
+
1239
+	if err != nil {
1240
+		c.logger.WithError(err).WithField("container", ctr.id).
1241
+			Debug("failed to shutdown container, terminating it")
1242
+		terminateErr := c.terminateContainer(ctr)
1243
+		if terminateErr != nil {
1244
+			c.logger.WithError(terminateErr).WithField("container", ctr.id).
1245
+				Error("failed to shutdown container, and subsequent terminate also failed")
1246
+			return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
1247
+		}
1248
+		return err
1249
+	}
1250
+
1251
+	return nil
1252
+}
1253
+
1254
+// ctr mutex must be held when calling this function.
1255
+func (c *client) terminateContainer(ctr *container) error {
1256
+	const terminateTimeout = time.Minute * 5
1257
+	ctr.terminateInvoked = true
1258
+	err := ctr.hcsContainer.Terminate()
1259
+
1260
+	if hcsshim.IsPending(err) {
1261
+		err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
1262
+	} else if hcsshim.IsAlreadyStopped(err) {
1263
+		err = nil
1264
+	}
1265
+
1266
+	if err != nil {
1267
+		c.logger.WithError(err).WithField("container", ctr.id).
1268
+			Debug("failed to terminate container")
1269
+		return err
1270
+	}
1271
+
1272
+	return nil
1273
+}
1274
+
1275
+func (c *client) reapProcess(ctr *container, p *process) int {
1276
+	logger := c.logger.WithFields(logrus.Fields{
1277
+		"container": ctr.id,
1278
+		"process":   p.id,
1279
+	})
1280
+
1281
+	var eventErr error
1282
+
1283
+	// Block indefinitely for the process to exit.
1284
+	if err := p.hcsProcess.Wait(); err != nil {
1285
+		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1286
+			logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
1287
+		}
1288
+		// Fall through here, do not return. This ensures we attempt to
1289
+		// continue the shutdown in HCS and tell the docker engine that the
1290
+		// process/container has exited to avoid a container being dropped on
1291
+		// the floor.
1292
+	}
1293
+	exitedAt := time.Now()
1294
+
1295
+	exitCode, err := p.hcsProcess.ExitCode()
1296
+	if err != nil {
1297
+		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1298
+			logger.WithError(err).Warnf("unable to get exit code for process")
1299
+		}
1300
+		// Since we got an error retrieving the exit code, make sure that the
1301
+		// code we return doesn't incorrectly indicate success.
1302
+		exitCode = -1
1303
+
1304
+		// Fall through here, do not return. This ensures we attempt to
1305
+		// continue the shutdown in HCS and tell the docker engine that the
1306
+		// process/container has exited to avoid a container being dropped on
1307
+		// the floor.
1308
+	}
1309
+
1310
+	if err := p.hcsProcess.Close(); err != nil {
1311
+		logger.WithError(err).Warnf("failed to cleanup hcs process resources")
1312
+		exitCode = -1
1313
+		eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
1314
+	}
1315
+
1316
+	if p.id == libcontainerdtypes.InitProcessName {
1317
+		// Update container status
1318
+		ctr.Lock()
1319
+		ctr.status = libcontainerdtypes.StatusStopped
1320
+		ctr.exitedAt = exitedAt
1321
+		ctr.exitCode = uint32(exitCode)
1322
+		close(ctr.waitCh)
1323
+
1324
+		if err := c.shutdownContainer(ctr); err != nil {
1325
+			exitCode = -1
1326
+			logger.WithError(err).Warn("failed to shutdown container")
1327
+			thisErr := fmt.Errorf("failed to shutdown container: %s", err)
1328
+			if eventErr != nil {
1329
+				eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
1330
+			} else {
1331
+				eventErr = thisErr
1332
+			}
1333
+		} else {
1334
+			logger.Debug("completed container shutdown")
1335
+		}
1336
+		ctr.Unlock()
1337
+
1338
+		if err := ctr.hcsContainer.Close(); err != nil {
1339
+			exitCode = -1
1340
+			logger.WithError(err).Error("failed to clean hcs container resources")
1341
+			thisErr := fmt.Errorf("failed to terminate container: %s", err)
1342
+			if eventErr != nil {
1343
+				eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
1344
+			} else {
1345
+				eventErr = thisErr
1346
+			}
1347
+		}
1348
+	}
1349
+
1350
+	c.eventQ.Append(ctr.id, func() {
1351
+		ei := libcontainerdtypes.EventInfo{
1352
+			ContainerID: ctr.id,
1353
+			ProcessID:   p.id,
1354
+			Pid:         uint32(p.pid),
1355
+			ExitCode:    uint32(exitCode),
1356
+			ExitedAt:    exitedAt,
1357
+			Error:       eventErr,
1358
+		}
1359
+		c.logger.WithFields(logrus.Fields{
1360
+			"container":  ctr.id,
1361
+			"event":      libcontainerdtypes.EventExit,
1362
+			"event-info": ei,
1363
+		}).Info("sending event")
1364
+		err := c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExit, ei)
1365
+		if err != nil {
1366
+			c.logger.WithError(err).WithFields(logrus.Fields{
1367
+				"container":  ctr.id,
1368
+				"event":      libcontainerdtypes.EventExit,
1369
+				"event-info": ei,
1370
+			}).Error("failed to process event")
1371
+		}
1372
+		if p.id != libcontainerdtypes.InitProcessName {
1373
+			ctr.Lock()
1374
+			delete(ctr.execs, p.id)
1375
+			ctr.Unlock()
1376
+		}
1377
+	})
1378
+
1379
+	return exitCode
1380
+}
0 1381
deleted file mode 100644
... ...
@@ -1,44 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"io"
5
-	"sync"
6
-
7
-	"github.com/Microsoft/hcsshim"
8
-	"github.com/docker/docker/pkg/ioutils"
9
-)
10
-
11
-type autoClosingReader struct {
12
-	io.ReadCloser
13
-	sync.Once
14
-}
15
-
16
-func (r *autoClosingReader) Read(b []byte) (n int, err error) {
17
-	n, err = r.ReadCloser.Read(b)
18
-	if err != nil {
19
-		r.Once.Do(func() { r.ReadCloser.Close() })
20
-	}
21
-	return
22
-}
23
-
24
-func createStdInCloser(pipe io.WriteCloser, process hcsshim.Process) io.WriteCloser {
25
-	return ioutils.NewWriteCloserWrapper(pipe, func() error {
26
-		if err := pipe.Close(); err != nil {
27
-			return err
28
-		}
29
-
30
-		err := process.CloseStdin()
31
-		if err != nil && !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyClosed(err) {
32
-			// This error will occur if the compute system is currently shutting down
33
-			if perr, ok := err.(*hcsshim.ProcessError); ok && perr.Err != hcsshim.ErrVmcomputeOperationInvalidState {
34
-				return err
35
-			}
36
-		}
37
-
38
-		return nil
39
-	})
40
-}
41
-
42
-func (p *process) Cleanup() error {
43
-	return nil
44
-}
45 1
deleted file mode 100644
... ...
@@ -1,35 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import "sync"
4
-
5
-type queue struct {
6
-	sync.Mutex
7
-	fns map[string]chan struct{}
8
-}
9
-
10
-func (q *queue) append(id string, f func()) {
11
-	q.Lock()
12
-	defer q.Unlock()
13
-
14
-	if q.fns == nil {
15
-		q.fns = make(map[string]chan struct{})
16
-	}
17
-
18
-	done := make(chan struct{})
19
-
20
-	fn, ok := q.fns[id]
21
-	q.fns[id] = done
22
-	go func() {
23
-		if ok {
24
-			<-fn
25
-		}
26
-		f()
27
-		close(done)
28
-
29
-		q.Lock()
30
-		if q.fns[id] == done {
31
-			delete(q.fns, id)
32
-		}
33
-		q.Unlock()
34
-	}()
35
-}
36 1
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package queue // import "github.com/docker/docker/libcontainerd/queue"
1
+
2
+import "sync"
3
+
4
+// Queue is the structure used for holding functions in a queue.
5
+type Queue struct {
6
+	sync.Mutex
7
+	fns map[string]chan struct{}
8
+}
9
+
10
+// Append adds an item to a queue.
11
+func (q *Queue) Append(id string, f func()) {
12
+	q.Lock()
13
+	defer q.Unlock()
14
+
15
+	if q.fns == nil {
16
+		q.fns = make(map[string]chan struct{})
17
+	}
18
+
19
+	done := make(chan struct{})
20
+
21
+	fn, ok := q.fns[id]
22
+	q.fns[id] = done
23
+	go func() {
24
+		if ok {
25
+			<-fn
26
+		}
27
+		f()
28
+		close(done)
29
+
30
+		q.Lock()
31
+		if q.fns[id] == done {
32
+			delete(q.fns, id)
33
+		}
34
+		q.Unlock()
35
+	}()
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package queue // import "github.com/docker/docker/libcontainerd/queue"
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+
6
+	"gotest.tools/assert"
7
+)
8
+
9
+func TestSerialization(t *testing.T) {
10
+	var (
11
+		q             Queue
12
+		serialization = 1
13
+	)
14
+
15
+	q.Append("aaa", func() {
16
+		//simulate a long time task
17
+		time.Sleep(10 * time.Millisecond)
18
+		assert.Equal(t, serialization, 1)
19
+		serialization = 2
20
+	})
21
+	q.Append("aaa", func() {
22
+		assert.Equal(t, serialization, 2)
23
+		serialization = 3
24
+	})
25
+	q.Append("aaa", func() {
26
+		assert.Equal(t, serialization, 3)
27
+		serialization = 4
28
+	})
29
+	time.Sleep(20 * time.Millisecond)
30
+}
0 31
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"testing"
5
-	"time"
6
-
7
-	"gotest.tools/assert"
8
-)
9
-
10
-func TestSerialization(t *testing.T) {
11
-	var (
12
-		q             queue
13
-		serialization = 1
14
-	)
15
-
16
-	q.append("aaa", func() {
17
-		//simulate a long time task
18
-		time.Sleep(10 * time.Millisecond)
19
-		assert.Equal(t, serialization, 1)
20
-		serialization = 2
21
-	})
22
-	q.append("aaa", func() {
23
-		assert.Equal(t, serialization, 2)
24
-		serialization = 3
25
-	})
26
-	q.append("aaa", func() {
27
-		assert.Equal(t, serialization, 3)
28
-		serialization = 4
29
-	})
30
-	time.Sleep(20 * time.Millisecond)
31
-}
32 1
new file mode 100644
... ...
@@ -0,0 +1,921 @@
0
+package remote // import "github.com/docker/docker/libcontainerd/remote"
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"io"
6
+	"os"
7
+	"path/filepath"
8
+	"reflect"
9
+	"runtime"
10
+	"strings"
11
+	"sync"
12
+	"syscall"
13
+	"time"
14
+
15
+	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
16
+	"github.com/containerd/containerd"
17
+	apievents "github.com/containerd/containerd/api/events"
18
+	"github.com/containerd/containerd/api/types"
19
+	"github.com/containerd/containerd/archive"
20
+	"github.com/containerd/containerd/cio"
21
+	"github.com/containerd/containerd/content"
22
+	containerderrors "github.com/containerd/containerd/errdefs"
23
+	"github.com/containerd/containerd/events"
24
+	"github.com/containerd/containerd/images"
25
+	"github.com/containerd/containerd/runtime/linux/runctypes"
26
+	"github.com/containerd/typeurl"
27
+	"github.com/docker/docker/errdefs"
28
+	"github.com/docker/docker/libcontainerd/queue"
29
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
30
+
31
+	"github.com/docker/docker/pkg/ioutils"
32
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
33
+	specs "github.com/opencontainers/runtime-spec/specs-go"
34
+	"github.com/pkg/errors"
35
+	"github.com/sirupsen/logrus"
36
+	"google.golang.org/grpc/codes"
37
+	"google.golang.org/grpc/status"
38
+)
39
+
40
+type container struct {
41
+	mu sync.Mutex
42
+
43
+	bundleDir string
44
+	ctr       containerd.Container
45
+	task      containerd.Task
46
+	execs     map[string]containerd.Process
47
+	oomKilled bool
48
+}
49
+
50
+func (c *container) setTask(t containerd.Task) {
51
+	c.mu.Lock()
52
+	c.task = t
53
+	c.mu.Unlock()
54
+}
55
+
56
+func (c *container) getTask() containerd.Task {
57
+	c.mu.Lock()
58
+	t := c.task
59
+	c.mu.Unlock()
60
+	return t
61
+}
62
+
63
+func (c *container) addProcess(id string, p containerd.Process) {
64
+	c.mu.Lock()
65
+	if c.execs == nil {
66
+		c.execs = make(map[string]containerd.Process)
67
+	}
68
+	c.execs[id] = p
69
+	c.mu.Unlock()
70
+}
71
+
72
+func (c *container) deleteProcess(id string) {
73
+	c.mu.Lock()
74
+	delete(c.execs, id)
75
+	c.mu.Unlock()
76
+}
77
+
78
+func (c *container) getProcess(id string) containerd.Process {
79
+	c.mu.Lock()
80
+	p := c.execs[id]
81
+	c.mu.Unlock()
82
+	return p
83
+}
84
+
85
+func (c *container) setOOMKilled(killed bool) {
86
+	c.mu.Lock()
87
+	c.oomKilled = killed
88
+	c.mu.Unlock()
89
+}
90
+
91
+func (c *container) getOOMKilled() bool {
92
+	c.mu.Lock()
93
+	killed := c.oomKilled
94
+	c.mu.Unlock()
95
+	return killed
96
+}
97
+
98
+type client struct {
99
+	sync.RWMutex // protects containers map
100
+
101
+	client   *containerd.Client
102
+	stateDir string
103
+	logger   *logrus.Entry
104
+	ns       string
105
+
106
+	backend    libcontainerdtypes.Backend
107
+	eventQ     queue.Queue
108
+	containers map[string]*container
109
+}
110
+
111
+// NewClient creates a new libcontainerd client from a containerd client
112
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
113
+	c := &client{
114
+		client:     cli,
115
+		stateDir:   stateDir,
116
+		logger:     logrus.WithField("module", "libcontainerd").WithField("namespace", ns),
117
+		ns:         ns,
118
+		backend:    b,
119
+		containers: make(map[string]*container),
120
+	}
121
+
122
+	go c.processEventStream(ctx, ns)
123
+
124
+	return c, nil
125
+}
126
+
127
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
128
+	return c.client.Version(ctx)
129
+}
130
+
131
+// Restore loads the containerd container.
132
+// It should not be called concurrently with any other operation for the given ID.
133
+func (c *client) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
134
+	c.Lock()
135
+	_, ok := c.containers[id]
136
+	if ok {
137
+		c.Unlock()
138
+		return false, 0, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
139
+	}
140
+
141
+	cntr := &container{}
142
+	c.containers[id] = cntr
143
+	cntr.mu.Lock()
144
+	defer cntr.mu.Unlock()
145
+
146
+	c.Unlock()
147
+
148
+	defer func() {
149
+		if err != nil {
150
+			c.Lock()
151
+			delete(c.containers, id)
152
+			c.Unlock()
153
+		}
154
+	}()
155
+
156
+	var dio *cio.DirectIO
157
+	defer func() {
158
+		if err != nil && dio != nil {
159
+			dio.Cancel()
160
+			dio.Close()
161
+		}
162
+		err = wrapError(err)
163
+	}()
164
+
165
+	ctr, err := c.client.LoadContainer(ctx, id)
166
+	if err != nil {
167
+		return false, -1, errors.WithStack(wrapError(err))
168
+	}
169
+
170
+	attachIO := func(fifos *cio.FIFOSet) (cio.IO, error) {
171
+		// dio must be assigned to the previously defined dio for the defer above
172
+		// to handle cleanup
173
+
174
+		dio, err = c.newDirectIO(ctx, fifos)
175
+		if err != nil {
176
+			return nil, err
177
+		}
178
+		return attachStdio(dio)
179
+	}
180
+	t, err := ctr.Task(ctx, attachIO)
181
+	if err != nil && !containerderrors.IsNotFound(err) {
182
+		return false, -1, errors.Wrap(wrapError(err), "error getting containerd task for container")
183
+	}
184
+
185
+	if t != nil {
186
+		s, err := t.Status(ctx)
187
+		if err != nil {
188
+			return false, -1, errors.Wrap(wrapError(err), "error getting task status")
189
+		}
190
+
191
+		alive = s.Status != containerd.Stopped
192
+		pid = int(t.Pid())
193
+	}
194
+
195
+	cntr.bundleDir = filepath.Join(c.stateDir, id)
196
+	cntr.ctr = ctr
197
+	cntr.task = t
198
+	// TODO(mlaventure): load execs
199
+
200
+	c.logger.WithFields(logrus.Fields{
201
+		"container": id,
202
+		"alive":     alive,
203
+		"pid":       pid,
204
+	}).Debug("restored container")
205
+
206
+	return alive, pid, nil
207
+}
208
+
209
+func (c *client) Create(ctx context.Context, id string, ociSpec *specs.Spec, runtimeOptions interface{}) error {
210
+	if ctr := c.getContainer(id); ctr != nil {
211
+		return errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
212
+	}
213
+
214
+	bdir, err := prepareBundleDir(filepath.Join(c.stateDir, id), ociSpec)
215
+	if err != nil {
216
+		return errdefs.System(errors.Wrap(err, "prepare bundle dir failed"))
217
+	}
218
+
219
+	c.logger.WithField("bundle", bdir).WithField("root", ociSpec.Root.Path).Debug("bundle dir created")
220
+
221
+	cdCtr, err := c.client.NewContainer(ctx, id,
222
+		containerd.WithSpec(ociSpec),
223
+		containerd.WithRuntime(runtimeName, runtimeOptions))
224
+	if err != nil {
225
+		return wrapError(err)
226
+	}
227
+
228
+	c.Lock()
229
+	c.containers[id] = &container{
230
+		bundleDir: bdir,
231
+		ctr:       cdCtr,
232
+	}
233
+	c.Unlock()
234
+
235
+	return nil
236
+}
237
+
238
+// Start create and start a task for the specified containerd id
239
+func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
240
+	ctr := c.getContainer(id)
241
+	if ctr == nil {
242
+		return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
243
+	}
244
+	if t := ctr.getTask(); t != nil {
245
+		return -1, errors.WithStack(errdefs.Conflict(errors.New("container already started")))
246
+	}
247
+
248
+	var (
249
+		cp             *types.Descriptor
250
+		t              containerd.Task
251
+		rio            cio.IO
252
+		err            error
253
+		stdinCloseSync = make(chan struct{})
254
+	)
255
+
256
+	if checkpointDir != "" {
257
+		// write checkpoint to the content store
258
+		tar := archive.Diff(ctx, "", checkpointDir)
259
+		cp, err = c.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, checkpointDir, tar)
260
+		// remove the checkpoint when we're done
261
+		defer func() {
262
+			if cp != nil {
263
+				err := c.client.ContentStore().Delete(context.Background(), cp.Digest)
264
+				if err != nil {
265
+					c.logger.WithError(err).WithFields(logrus.Fields{
266
+						"ref":    checkpointDir,
267
+						"digest": cp.Digest,
268
+					}).Warnf("failed to delete temporary checkpoint entry")
269
+				}
270
+			}
271
+		}()
272
+		if err := tar.Close(); err != nil {
273
+			return -1, errors.Wrap(err, "failed to close checkpoint tar stream")
274
+		}
275
+		if err != nil {
276
+			return -1, errors.Wrapf(err, "failed to upload checkpoint to containerd")
277
+		}
278
+	}
279
+
280
+	spec, err := ctr.ctr.Spec(ctx)
281
+	if err != nil {
282
+		return -1, errors.Wrap(err, "failed to retrieve spec")
283
+	}
284
+	uid, gid := getSpecUser(spec)
285
+	t, err = ctr.ctr.NewTask(ctx,
286
+		func(id string) (cio.IO, error) {
287
+			fifos := newFIFOSet(ctr.bundleDir, libcontainerdtypes.InitProcessName, withStdin, spec.Process.Terminal)
288
+
289
+			rio, err = c.createIO(fifos, id, libcontainerdtypes.InitProcessName, stdinCloseSync, attachStdio)
290
+			return rio, err
291
+		},
292
+		func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
293
+			info.Checkpoint = cp
294
+			if runtime.GOOS != "windows" {
295
+				info.Options = &runctypes.CreateOptions{
296
+					IoUid:       uint32(uid),
297
+					IoGid:       uint32(gid),
298
+					NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
299
+				}
300
+			} else {
301
+				// Make sure we set the runhcs options to debug if we are at debug level.
302
+				if c.logger.Level == logrus.DebugLevel {
303
+					info.Options = &options.Options{Debug: true}
304
+				}
305
+			}
306
+			return nil
307
+		})
308
+	if err != nil {
309
+		close(stdinCloseSync)
310
+		if rio != nil {
311
+			rio.Cancel()
312
+			rio.Close()
313
+		}
314
+		return -1, wrapError(err)
315
+	}
316
+
317
+	ctr.setTask(t)
318
+
319
+	// Signal c.createIO that it can call CloseIO
320
+	close(stdinCloseSync)
321
+
322
+	if err := t.Start(ctx); err != nil {
323
+		if _, err := t.Delete(ctx); err != nil {
324
+			c.logger.WithError(err).WithField("container", id).
325
+				Error("failed to delete task after fail start")
326
+		}
327
+		ctr.setTask(nil)
328
+		return -1, wrapError(err)
329
+	}
330
+
331
+	return int(t.Pid()), nil
332
+}
333
+
334
+// Exec creates exec process.
335
+//
336
+// The containerd client calls Exec to register the exec config in the shim side.
337
+// When the client calls Start, the shim will create stdin fifo if needs. But
338
+// for the container main process, the stdin fifo will be created in Create not
339
+// the Start call. stdinCloseSync channel should be closed after Start exec
340
+// process.
341
+func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
342
+	ctr := c.getContainer(containerID)
343
+	if ctr == nil {
344
+		return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
345
+	}
346
+	t := ctr.getTask()
347
+	if t == nil {
348
+		return -1, errors.WithStack(errdefs.InvalidParameter(errors.New("container is not running")))
349
+	}
350
+
351
+	if p := ctr.getProcess(processID); p != nil {
352
+		return -1, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
353
+	}
354
+
355
+	var (
356
+		p              containerd.Process
357
+		rio            cio.IO
358
+		err            error
359
+		stdinCloseSync = make(chan struct{})
360
+	)
361
+
362
+	fifos := newFIFOSet(ctr.bundleDir, processID, withStdin, spec.Terminal)
363
+
364
+	defer func() {
365
+		if err != nil {
366
+			if rio != nil {
367
+				rio.Cancel()
368
+				rio.Close()
369
+			}
370
+		}
371
+	}()
372
+
373
+	p, err = t.Exec(ctx, processID, spec, func(id string) (cio.IO, error) {
374
+		rio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio)
375
+		return rio, err
376
+	})
377
+	if err != nil {
378
+		close(stdinCloseSync)
379
+		return -1, wrapError(err)
380
+	}
381
+
382
+	ctr.addProcess(processID, p)
383
+
384
+	// Signal c.createIO that it can call CloseIO
385
+	//
386
+	// the stdin of exec process will be created after p.Start in containerd
387
+	defer close(stdinCloseSync)
388
+
389
+	if err = p.Start(ctx); err != nil {
390
+		// use new context for cleanup because old one may be cancelled by user, but leave a timeout to make sure
391
+		// we are not waiting forever if containerd is unresponsive or to work around fifo cancelling issues in
392
+		// older containerd-shim
393
+		ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
394
+		defer cancel()
395
+		p.Delete(ctx)
396
+		ctr.deleteProcess(processID)
397
+		return -1, wrapError(err)
398
+	}
399
+
400
+	return int(p.Pid()), nil
401
+}
402
+
403
+func (c *client) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
404
+	p, err := c.getProcess(containerID, processID)
405
+	if err != nil {
406
+		return err
407
+	}
408
+	return wrapError(p.Kill(ctx, syscall.Signal(signal)))
409
+}
410
+
411
+func (c *client) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
412
+	p, err := c.getProcess(containerID, processID)
413
+	if err != nil {
414
+		return err
415
+	}
416
+
417
+	return p.Resize(ctx, uint32(width), uint32(height))
418
+}
419
+
420
+func (c *client) CloseStdin(ctx context.Context, containerID, processID string) error {
421
+	p, err := c.getProcess(containerID, processID)
422
+	if err != nil {
423
+		return err
424
+	}
425
+
426
+	return p.CloseIO(ctx, containerd.WithStdinCloser)
427
+}
428
+
429
+func (c *client) Pause(ctx context.Context, containerID string) error {
430
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
431
+	if err != nil {
432
+		return err
433
+	}
434
+
435
+	return wrapError(p.(containerd.Task).Pause(ctx))
436
+}
437
+
438
+func (c *client) Resume(ctx context.Context, containerID string) error {
439
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
440
+	if err != nil {
441
+		return err
442
+	}
443
+
444
+	return p.(containerd.Task).Resume(ctx)
445
+}
446
+
447
+func (c *client) Stats(ctx context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
448
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
449
+	if err != nil {
450
+		return nil, err
451
+	}
452
+
453
+	m, err := p.(containerd.Task).Metrics(ctx)
454
+	if err != nil {
455
+		return nil, err
456
+	}
457
+
458
+	v, err := typeurl.UnmarshalAny(m.Data)
459
+	if err != nil {
460
+		return nil, err
461
+	}
462
+	return libcontainerdtypes.InterfaceToStats(m.Timestamp, v), nil
463
+}
464
+
465
+func (c *client) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
466
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
467
+	if err != nil {
468
+		return nil, err
469
+	}
470
+
471
+	pis, err := p.(containerd.Task).Pids(ctx)
472
+	if err != nil {
473
+		return nil, err
474
+	}
475
+
476
+	var pids []uint32
477
+	for _, i := range pis {
478
+		pids = append(pids, i.Pid)
479
+	}
480
+
481
+	return pids, nil
482
+}
483
+
484
+func (c *client) Summary(ctx context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
485
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
486
+	if err != nil {
487
+		return nil, err
488
+	}
489
+
490
+	pis, err := p.(containerd.Task).Pids(ctx)
491
+	if err != nil {
492
+		return nil, err
493
+	}
494
+
495
+	var infos []libcontainerdtypes.Summary
496
+	for _, pi := range pis {
497
+		i, err := typeurl.UnmarshalAny(pi.Info)
498
+		if err != nil {
499
+			return nil, errors.Wrap(err, "unable to decode process details")
500
+		}
501
+		s, err := summaryFromInterface(i)
502
+		if err != nil {
503
+			return nil, err
504
+		}
505
+		infos = append(infos, *s)
506
+	}
507
+
508
+	return infos, nil
509
+}
510
+
511
+func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
512
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
513
+	if err != nil {
514
+		return 255, time.Now(), nil
515
+	}
516
+
517
+	status, err := p.(containerd.Task).Delete(ctx)
518
+	if err != nil {
519
+		return 255, time.Now(), nil
520
+	}
521
+
522
+	if ctr := c.getContainer(containerID); ctr != nil {
523
+		ctr.setTask(nil)
524
+	}
525
+	return status.ExitCode(), status.ExitTime(), nil
526
+}
527
+
528
+func (c *client) Delete(ctx context.Context, containerID string) error {
529
+	ctr := c.getContainer(containerID)
530
+	if ctr == nil {
531
+		return errors.WithStack(errdefs.NotFound(errors.New("no such container")))
532
+	}
533
+
534
+	if err := ctr.ctr.Delete(ctx); err != nil {
535
+		return wrapError(err)
536
+	}
537
+
538
+	if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" {
539
+		if err := os.RemoveAll(ctr.bundleDir); err != nil {
540
+			c.logger.WithError(err).WithFields(logrus.Fields{
541
+				"container": containerID,
542
+				"bundle":    ctr.bundleDir,
543
+			}).Error("failed to remove state dir")
544
+		}
545
+	}
546
+
547
+	c.removeContainer(containerID)
548
+
549
+	return nil
550
+}
551
+
552
+func (c *client) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
553
+	ctr := c.getContainer(containerID)
554
+	if ctr == nil {
555
+		return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
556
+	}
557
+
558
+	t := ctr.getTask()
559
+	if t == nil {
560
+		return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such task")))
561
+	}
562
+
563
+	s, err := t.Status(ctx)
564
+	if err != nil {
565
+		return libcontainerdtypes.StatusUnknown, wrapError(err)
566
+	}
567
+
568
+	return libcontainerdtypes.Status(s.Status), nil
569
+}
570
+
571
+func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
572
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
573
+	if err != nil {
574
+		return err
575
+	}
576
+
577
+	opts := []containerd.CheckpointTaskOpts{}
578
+	if exit {
579
+		opts = append(opts, func(r *containerd.CheckpointTaskInfo) error {
580
+			if r.Options == nil {
581
+				r.Options = &runctypes.CheckpointOptions{
582
+					Exit: true,
583
+				}
584
+			} else {
585
+				opts, _ := r.Options.(*runctypes.CheckpointOptions)
586
+				opts.Exit = true
587
+			}
588
+			return nil
589
+		})
590
+	}
591
+	img, err := p.(containerd.Task).Checkpoint(ctx, opts...)
592
+	if err != nil {
593
+		return wrapError(err)
594
+	}
595
+	// Whatever happens, delete the checkpoint from containerd
596
+	defer func() {
597
+		err := c.client.ImageService().Delete(context.Background(), img.Name())
598
+		if err != nil {
599
+			c.logger.WithError(err).WithField("digest", img.Target().Digest).
600
+				Warnf("failed to delete checkpoint image")
601
+		}
602
+	}()
603
+
604
+	b, err := content.ReadBlob(ctx, c.client.ContentStore(), img.Target())
605
+	if err != nil {
606
+		return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data"))
607
+	}
608
+	var index v1.Index
609
+	if err := json.Unmarshal(b, &index); err != nil {
610
+		return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data"))
611
+	}
612
+
613
+	var cpDesc *v1.Descriptor
614
+	for _, m := range index.Manifests {
615
+		if m.MediaType == images.MediaTypeContainerd1Checkpoint {
616
+			cpDesc = &m
617
+			break
618
+		}
619
+	}
620
+	if cpDesc == nil {
621
+		return errdefs.System(errors.Wrapf(err, "invalid checkpoint"))
622
+	}
623
+
624
+	rat, err := c.client.ContentStore().ReaderAt(ctx, *cpDesc)
625
+	if err != nil {
626
+		return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader"))
627
+	}
628
+	defer rat.Close()
629
+	_, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat))
630
+	if err != nil {
631
+		return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader"))
632
+	}
633
+
634
+	return err
635
+}
636
+
637
+func (c *client) getContainer(id string) *container {
638
+	c.RLock()
639
+	ctr := c.containers[id]
640
+	c.RUnlock()
641
+
642
+	return ctr
643
+}
644
+
645
+func (c *client) removeContainer(id string) {
646
+	c.Lock()
647
+	delete(c.containers, id)
648
+	c.Unlock()
649
+}
650
+
651
+func (c *client) getProcess(containerID, processID string) (containerd.Process, error) {
652
+	ctr := c.getContainer(containerID)
653
+	if ctr == nil {
654
+		return nil, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
655
+	}
656
+
657
+	t := ctr.getTask()
658
+	if t == nil {
659
+		return nil, errors.WithStack(errdefs.NotFound(errors.New("container is not running")))
660
+	}
661
+	if processID == libcontainerdtypes.InitProcessName {
662
+		return t, nil
663
+	}
664
+
665
+	p := ctr.getProcess(processID)
666
+	if p == nil {
667
+		return nil, errors.WithStack(errdefs.NotFound(errors.New("no such exec")))
668
+	}
669
+	return p, nil
670
+}
671
+
672
+// createIO creates the io to be used by a process
673
+// This needs to get a pointer to interface as upon closure the process may not have yet been registered
674
+func (c *client) createIO(fifos *cio.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio libcontainerdtypes.StdioCallback) (cio.IO, error) {
675
+	var (
676
+		io  *cio.DirectIO
677
+		err error
678
+	)
679
+	io, err = c.newDirectIO(context.Background(), fifos)
680
+	if err != nil {
681
+		return nil, err
682
+	}
683
+
684
+	if io.Stdin != nil {
685
+		var (
686
+			err       error
687
+			stdinOnce sync.Once
688
+		)
689
+		pipe := io.Stdin
690
+		io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error {
691
+			stdinOnce.Do(func() {
692
+				err = pipe.Close()
693
+				// Do the rest in a new routine to avoid a deadlock if the
694
+				// Exec/Start call failed.
695
+				go func() {
696
+					<-stdinCloseSync
697
+					p, err := c.getProcess(containerID, processID)
698
+					if err == nil {
699
+						err = p.CloseIO(context.Background(), containerd.WithStdinCloser)
700
+						if err != nil && strings.Contains(err.Error(), "transport is closing") {
701
+							err = nil
702
+						}
703
+					}
704
+				}()
705
+			})
706
+			return err
707
+		})
708
+	}
709
+
710
+	rio, err := attachStdio(io)
711
+	if err != nil {
712
+		io.Cancel()
713
+		io.Close()
714
+	}
715
+	return rio, err
716
+}
717
+
718
+func (c *client) processEvent(ctr *container, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) {
719
+	c.eventQ.Append(ei.ContainerID, func() {
720
+		err := c.backend.ProcessEvent(ei.ContainerID, et, ei)
721
+		if err != nil {
722
+			c.logger.WithError(err).WithFields(logrus.Fields{
723
+				"container":  ei.ContainerID,
724
+				"event":      et,
725
+				"event-info": ei,
726
+			}).Error("failed to process event")
727
+		}
728
+
729
+		if et == libcontainerdtypes.EventExit && ei.ProcessID != ei.ContainerID {
730
+			p := ctr.getProcess(ei.ProcessID)
731
+			if p == nil {
732
+				c.logger.WithError(errors.New("no such process")).
733
+					WithFields(logrus.Fields{
734
+						"container": ei.ContainerID,
735
+						"process":   ei.ProcessID,
736
+					}).Error("exit event")
737
+				return
738
+			}
739
+			_, err = p.Delete(context.Background())
740
+			if err != nil {
741
+				c.logger.WithError(err).WithFields(logrus.Fields{
742
+					"container": ei.ContainerID,
743
+					"process":   ei.ProcessID,
744
+				}).Warn("failed to delete process")
745
+			}
746
+			ctr.deleteProcess(ei.ProcessID)
747
+
748
+			ctr := c.getContainer(ei.ContainerID)
749
+			if ctr == nil {
750
+				c.logger.WithFields(logrus.Fields{
751
+					"container": ei.ContainerID,
752
+				}).Error("failed to find container")
753
+			} else {
754
+				newFIFOSet(ctr.bundleDir, ei.ProcessID, true, false).Close()
755
+			}
756
+		}
757
+	})
758
+}
759
+
760
+func (c *client) processEventStream(ctx context.Context, ns string) {
761
+	var (
762
+		err error
763
+		ev  *events.Envelope
764
+		et  libcontainerdtypes.EventType
765
+		ei  libcontainerdtypes.EventInfo
766
+		ctr *container
767
+	)
768
+
769
+	// Filter on both namespace *and* topic. To create an "and" filter,
770
+	// this must be a single, comma-separated string
771
+	eventStream, errC := c.client.EventService().Subscribe(ctx, "namespace=="+ns+",topic~=|^/tasks/|")
772
+
773
+	c.logger.Debug("processing event stream")
774
+
775
+	var oomKilled bool
776
+	for {
777
+		select {
778
+		case err = <-errC:
779
+			if err != nil {
780
+				errStatus, ok := status.FromError(err)
781
+				if !ok || errStatus.Code() != codes.Canceled {
782
+					c.logger.WithError(err).Error("failed to get event")
783
+					go c.processEventStream(ctx, ns)
784
+				} else {
785
+					c.logger.WithError(ctx.Err()).Info("stopping event stream following graceful shutdown")
786
+				}
787
+			}
788
+			return
789
+		case ev = <-eventStream:
790
+			if ev.Event == nil {
791
+				c.logger.WithField("event", ev).Warn("invalid event")
792
+				continue
793
+			}
794
+
795
+			v, err := typeurl.UnmarshalAny(ev.Event)
796
+			if err != nil {
797
+				c.logger.WithError(err).WithField("event", ev).Warn("failed to unmarshal event")
798
+				continue
799
+			}
800
+
801
+			c.logger.WithField("topic", ev.Topic).Debug("event")
802
+
803
+			switch t := v.(type) {
804
+			case *apievents.TaskCreate:
805
+				et = libcontainerdtypes.EventCreate
806
+				ei = libcontainerdtypes.EventInfo{
807
+					ContainerID: t.ContainerID,
808
+					ProcessID:   t.ContainerID,
809
+					Pid:         t.Pid,
810
+				}
811
+			case *apievents.TaskStart:
812
+				et = libcontainerdtypes.EventStart
813
+				ei = libcontainerdtypes.EventInfo{
814
+					ContainerID: t.ContainerID,
815
+					ProcessID:   t.ContainerID,
816
+					Pid:         t.Pid,
817
+				}
818
+			case *apievents.TaskExit:
819
+				et = libcontainerdtypes.EventExit
820
+				ei = libcontainerdtypes.EventInfo{
821
+					ContainerID: t.ContainerID,
822
+					ProcessID:   t.ID,
823
+					Pid:         t.Pid,
824
+					ExitCode:    t.ExitStatus,
825
+					ExitedAt:    t.ExitedAt,
826
+				}
827
+			case *apievents.TaskOOM:
828
+				et = libcontainerdtypes.EventOOM
829
+				ei = libcontainerdtypes.EventInfo{
830
+					ContainerID: t.ContainerID,
831
+					OOMKilled:   true,
832
+				}
833
+				oomKilled = true
834
+			case *apievents.TaskExecAdded:
835
+				et = libcontainerdtypes.EventExecAdded
836
+				ei = libcontainerdtypes.EventInfo{
837
+					ContainerID: t.ContainerID,
838
+					ProcessID:   t.ExecID,
839
+				}
840
+			case *apievents.TaskExecStarted:
841
+				et = libcontainerdtypes.EventExecStarted
842
+				ei = libcontainerdtypes.EventInfo{
843
+					ContainerID: t.ContainerID,
844
+					ProcessID:   t.ExecID,
845
+					Pid:         t.Pid,
846
+				}
847
+			case *apievents.TaskPaused:
848
+				et = libcontainerdtypes.EventPaused
849
+				ei = libcontainerdtypes.EventInfo{
850
+					ContainerID: t.ContainerID,
851
+				}
852
+			case *apievents.TaskResumed:
853
+				et = libcontainerdtypes.EventResumed
854
+				ei = libcontainerdtypes.EventInfo{
855
+					ContainerID: t.ContainerID,
856
+				}
857
+			default:
858
+				c.logger.WithFields(logrus.Fields{
859
+					"topic": ev.Topic,
860
+					"type":  reflect.TypeOf(t)},
861
+				).Info("ignoring event")
862
+				continue
863
+			}
864
+
865
+			ctr = c.getContainer(ei.ContainerID)
866
+			if ctr == nil {
867
+				c.logger.WithField("container", ei.ContainerID).Warn("unknown container")
868
+				continue
869
+			}
870
+
871
+			if oomKilled {
872
+				ctr.setOOMKilled(true)
873
+				oomKilled = false
874
+			}
875
+			ei.OOMKilled = ctr.getOOMKilled()
876
+
877
+			c.processEvent(ctr, et, ei)
878
+		}
879
+	}
880
+}
881
+
882
+func (c *client) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
883
+	writer, err := c.client.ContentStore().Writer(ctx, content.WithRef(ref))
884
+	if err != nil {
885
+		return nil, err
886
+	}
887
+	defer writer.Close()
888
+	size, err := io.Copy(writer, r)
889
+	if err != nil {
890
+		return nil, err
891
+	}
892
+	labels := map[string]string{
893
+		"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
894
+	}
895
+	if err := writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
896
+		return nil, err
897
+	}
898
+	return &types.Descriptor{
899
+		MediaType: mediaType,
900
+		Digest:    writer.Digest(),
901
+		Size_:     size,
902
+	}, nil
903
+}
904
+
905
+func wrapError(err error) error {
906
+	switch {
907
+	case err == nil:
908
+		return nil
909
+	case containerderrors.IsNotFound(err):
910
+		return errdefs.NotFound(err)
911
+	}
912
+
913
+	msg := err.Error()
914
+	for _, s := range []string{"container does not exist", "not found", "no such container"} {
915
+		if strings.Contains(msg, s) {
916
+			return errdefs.NotFound(err)
917
+		}
918
+	}
919
+	return err
920
+}
0 921
new file mode 100644
... ...
@@ -0,0 +1,161 @@
0
+package remote // import "github.com/docker/docker/libcontainerd/remote"
1
+
2
+import (
3
+	"io"
4
+	"net"
5
+	"sync"
6
+
7
+	winio "github.com/Microsoft/go-winio"
8
+	"github.com/containerd/containerd/cio"
9
+	"github.com/pkg/errors"
10
+	"github.com/sirupsen/logrus"
11
+	//	"golang.org/x/net/context"
12
+)
13
+
14
+type delayedConnection struct {
15
+	l    net.Listener
16
+	con  net.Conn
17
+	wg   sync.WaitGroup
18
+	once sync.Once
19
+}
20
+
21
+func (dc *delayedConnection) Write(p []byte) (int, error) {
22
+	dc.wg.Wait()
23
+	if dc.con != nil {
24
+		return dc.con.Write(p)
25
+	}
26
+	return 0, errors.New("use of closed network connection")
27
+}
28
+
29
+func (dc *delayedConnection) Read(p []byte) (int, error) {
30
+	dc.wg.Wait()
31
+	if dc.con != nil {
32
+		return dc.con.Read(p)
33
+	}
34
+	return 0, errors.New("use of closed network connection")
35
+}
36
+
37
+func (dc *delayedConnection) unblockConnectionWaiters() {
38
+	defer dc.once.Do(func() {
39
+		dc.wg.Done()
40
+	})
41
+}
42
+
43
+func (dc *delayedConnection) Close() error {
44
+	dc.l.Close()
45
+	if dc.con != nil {
46
+		return dc.con.Close()
47
+	}
48
+	dc.unblockConnectionWaiters()
49
+	return nil
50
+}
51
+
52
+type stdioPipes struct {
53
+	stdin  io.WriteCloser
54
+	stdout io.ReadCloser
55
+	stderr io.ReadCloser
56
+}
57
+
58
+// newStdioPipes creates actual fifos for stdio.
59
+func (c *client) newStdioPipes(fifos *cio.FIFOSet) (_ *stdioPipes, err error) {
60
+	p := &stdioPipes{}
61
+	if fifos.Stdin != "" {
62
+		c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("listen")
63
+		l, err := winio.ListenPipe(fifos.Stdin, nil)
64
+		if err != nil {
65
+			return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin)
66
+		}
67
+		dc := &delayedConnection{
68
+			l: l,
69
+		}
70
+		dc.wg.Add(1)
71
+		defer func() {
72
+			if err != nil {
73
+				dc.Close()
74
+			}
75
+		}()
76
+		p.stdin = dc
77
+
78
+		go func() {
79
+			c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("accept")
80
+			conn, err := l.Accept()
81
+			if err != nil {
82
+				dc.Close()
83
+				if err != winio.ErrPipeListenerClosed {
84
+					c.logger.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.Stdin)
85
+				}
86
+				return
87
+			}
88
+			c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("connected")
89
+			dc.con = conn
90
+			dc.unblockConnectionWaiters()
91
+		}()
92
+	}
93
+
94
+	if fifos.Stdout != "" {
95
+		c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("listen")
96
+		l, err := winio.ListenPipe(fifos.Stdout, nil)
97
+		if err != nil {
98
+			return nil, errors.Wrapf(err, "failed to create stdout pipe %s", fifos.Stdout)
99
+		}
100
+		dc := &delayedConnection{
101
+			l: l,
102
+		}
103
+		dc.wg.Add(1)
104
+		defer func() {
105
+			if err != nil {
106
+				dc.Close()
107
+			}
108
+		}()
109
+		p.stdout = dc
110
+
111
+		go func() {
112
+			c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("accept")
113
+			conn, err := l.Accept()
114
+			if err != nil {
115
+				dc.Close()
116
+				if err != winio.ErrPipeListenerClosed {
117
+					c.logger.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Stdout)
118
+				}
119
+				return
120
+			}
121
+			c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("connected")
122
+			dc.con = conn
123
+			dc.unblockConnectionWaiters()
124
+		}()
125
+	}
126
+
127
+	if fifos.Stderr != "" {
128
+		c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("listen")
129
+		l, err := winio.ListenPipe(fifos.Stderr, nil)
130
+		if err != nil {
131
+			return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr)
132
+		}
133
+		dc := &delayedConnection{
134
+			l: l,
135
+		}
136
+		dc.wg.Add(1)
137
+		defer func() {
138
+			if err != nil {
139
+				dc.Close()
140
+			}
141
+		}()
142
+		p.stderr = dc
143
+
144
+		go func() {
145
+			c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("accept")
146
+			conn, err := l.Accept()
147
+			if err != nil {
148
+				dc.Close()
149
+				if err != winio.ErrPipeListenerClosed {
150
+					c.logger.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Stderr)
151
+				}
152
+				return
153
+			}
154
+			c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("connected")
155
+			dc.con = conn
156
+			dc.unblockConnectionWaiters()
157
+		}()
158
+	}
159
+	return p, nil
160
+}
0 161
new file mode 100644
... ...
@@ -0,0 +1,115 @@
0
+package remote // import "github.com/docker/docker/libcontainerd/remote"
1
+
2
+import (
3
+	"context"
4
+	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+	"strings"
8
+
9
+	"github.com/containerd/containerd"
10
+	"github.com/containerd/containerd/cio"
11
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
12
+	"github.com/docker/docker/pkg/idtools"
13
+	"github.com/opencontainers/runtime-spec/specs-go"
14
+	"github.com/sirupsen/logrus"
15
+)
16
+
17
+const runtimeName = "io.containerd.runtime.v1.linux"
18
+
19
+func summaryFromInterface(i interface{}) (*libcontainerdtypes.Summary, error) {
20
+	return &libcontainerdtypes.Summary{}, nil
21
+}
22
+
23
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
24
+	p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName)
25
+	if err != nil {
26
+		return err
27
+	}
28
+
29
+	// go doesn't like the alias in 1.8, this means this need to be
30
+	// platform specific
31
+	return p.(containerd.Task).Update(ctx, containerd.WithResources((*specs.LinuxResources)(resources)))
32
+}
33
+
34
+func hostIDFromMap(id uint32, mp []specs.LinuxIDMapping) int {
35
+	for _, m := range mp {
36
+		if id >= m.ContainerID && id <= m.ContainerID+m.Size-1 {
37
+			return int(m.HostID + id - m.ContainerID)
38
+		}
39
+	}
40
+	return 0
41
+}
42
+
43
+func getSpecUser(ociSpec *specs.Spec) (int, int) {
44
+	var (
45
+		uid int
46
+		gid int
47
+	)
48
+
49
+	for _, ns := range ociSpec.Linux.Namespaces {
50
+		if ns.Type == specs.UserNamespace {
51
+			uid = hostIDFromMap(0, ociSpec.Linux.UIDMappings)
52
+			gid = hostIDFromMap(0, ociSpec.Linux.GIDMappings)
53
+			break
54
+		}
55
+	}
56
+
57
+	return uid, gid
58
+}
59
+
60
+func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
61
+	uid, gid := getSpecUser(ociSpec)
62
+	if uid == 0 && gid == 0 {
63
+		return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.Identity{UID: 0, GID: 0})
64
+	}
65
+
66
+	p := string(filepath.Separator)
67
+	components := strings.Split(bundleDir, string(filepath.Separator))
68
+	for _, d := range components[1:] {
69
+		p = filepath.Join(p, d)
70
+		fi, err := os.Stat(p)
71
+		if err != nil && !os.IsNotExist(err) {
72
+			return "", err
73
+		}
74
+		if os.IsNotExist(err) || fi.Mode()&1 == 0 {
75
+			p = fmt.Sprintf("%s.%d.%d", p, uid, gid)
76
+			if err := idtools.MkdirAndChown(p, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) {
77
+				return "", err
78
+			}
79
+		}
80
+	}
81
+
82
+	return p, nil
83
+}
84
+
85
+func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
86
+	config := cio.Config{
87
+		Terminal: withTerminal,
88
+		Stdout:   filepath.Join(bundleDir, processID+"-stdout"),
89
+	}
90
+	paths := []string{config.Stdout}
91
+
92
+	if withStdin {
93
+		config.Stdin = filepath.Join(bundleDir, processID+"-stdin")
94
+		paths = append(paths, config.Stdin)
95
+	}
96
+	if !withTerminal {
97
+		config.Stderr = filepath.Join(bundleDir, processID+"-stderr")
98
+		paths = append(paths, config.Stderr)
99
+	}
100
+	closer := func() error {
101
+		for _, path := range paths {
102
+			if err := os.RemoveAll(path); err != nil {
103
+				logrus.Warnf("libcontainerd: failed to remove fifo %v: %v", path, err)
104
+			}
105
+		}
106
+		return nil
107
+	}
108
+
109
+	return cio.NewFIFOSet(config, closer)
110
+}
111
+
112
+func (c *client) newDirectIO(ctx context.Context, fifos *cio.FIFOSet) (*cio.DirectIO, error) {
113
+	return cio.NewDirectIO(ctx, fifos)
114
+}
0 115
new file mode 100644
... ...
@@ -0,0 +1,80 @@
0
+package remote // import "github.com/docker/docker/libcontainerd/remote"
1
+
2
+import (
3
+	"context"
4
+	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+
8
+	"github.com/containerd/containerd/cio"
9
+	"github.com/containerd/containerd/windows/hcsshimtypes"
10
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
11
+	specs "github.com/opencontainers/runtime-spec/specs-go"
12
+	"github.com/pkg/errors"
13
+)
14
+
15
+const runtimeName = "io.containerd.runhcs.v1"
16
+
17
+func summaryFromInterface(i interface{}) (*libcontainerdtypes.Summary, error) {
18
+	switch pd := i.(type) {
19
+	case *hcsshimtypes.ProcessDetails:
20
+		return &libcontainerdtypes.Summary{
21
+			CreateTimestamp:              pd.CreatedAt,
22
+			ImageName:                    pd.ImageName,
23
+			KernelTime100ns:              pd.KernelTime_100Ns,
24
+			MemoryCommitBytes:            pd.MemoryCommitBytes,
25
+			MemoryWorkingSetPrivateBytes: pd.MemoryWorkingSetPrivateBytes,
26
+			MemoryWorkingSetSharedBytes:  pd.MemoryWorkingSetSharedBytes,
27
+			ProcessId:                    pd.ProcessID,
28
+			UserTime100ns:                pd.UserTime_100Ns,
29
+		}, nil
30
+	default:
31
+		return nil, errors.Errorf("Unknown process details type %T", pd)
32
+	}
33
+}
34
+
35
+func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
36
+	// TODO: (containerd) Determine if we need to use system.MkdirAllWithACL here
37
+	return bundleDir, os.MkdirAll(bundleDir, 0755)
38
+}
39
+
40
+func pipeName(containerID, processID, name string) string {
41
+	return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name)
42
+}
43
+
44
+func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
45
+	containerID := filepath.Base(bundleDir)
46
+	config := cio.Config{
47
+		Terminal: withTerminal,
48
+		Stdout:   pipeName(containerID, processID, "stdout"),
49
+	}
50
+
51
+	if withStdin {
52
+		config.Stdin = pipeName(containerID, processID, "stdin")
53
+	}
54
+
55
+	if !config.Terminal {
56
+		config.Stderr = pipeName(containerID, processID, "stderr")
57
+	}
58
+
59
+	return cio.NewFIFOSet(config, nil)
60
+}
61
+
62
+func (c *client) newDirectIO(ctx context.Context, fifos *cio.FIFOSet) (*cio.DirectIO, error) {
63
+	pipes, err := c.newStdioPipes(fifos)
64
+	if err != nil {
65
+		return nil, err
66
+	}
67
+	return cio.NewDirectIOFromFIFOSet(ctx, pipes.stdin, pipes.stdout, pipes.stderr, fifos), nil
68
+}
69
+
70
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
71
+	// TODO: (containerd): Not implemented, but don't error.
72
+	return nil
73
+}
74
+
75
+func getSpecUser(ociSpec *specs.Spec) (int, int) {
76
+	// TODO: (containerd): Not implemented, but don't error.
77
+	// Not clear if we can even do this for LCOW.
78
+	return 0, 0
79
+}
0 80
deleted file mode 100644
... ...
@@ -1,91 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"context"
5
-	"time"
6
-
7
-	"github.com/containerd/containerd"
8
-	"github.com/containerd/containerd/cio"
9
-	"github.com/opencontainers/runtime-spec/specs-go"
10
-)
11
-
12
-// EventType represents a possible event from libcontainerd
13
-type EventType string
14
-
15
-// Event constants used when reporting events
16
-const (
17
-	EventUnknown     EventType = "unknown"
18
-	EventExit        EventType = "exit"
19
-	EventOOM         EventType = "oom"
20
-	EventCreate      EventType = "create"
21
-	EventStart       EventType = "start"
22
-	EventExecAdded   EventType = "exec-added"
23
-	EventExecStarted EventType = "exec-started"
24
-	EventPaused      EventType = "paused"
25
-	EventResumed     EventType = "resumed"
26
-)
27
-
28
-// Status represents the current status of a container
29
-type Status string
30
-
31
-// Possible container statuses
32
-const (
33
-	// Running indicates the process is currently executing
34
-	StatusRunning Status = "running"
35
-	// Created indicates the process has been created within containerd but the
36
-	// user's defined process has not started
37
-	StatusCreated Status = "created"
38
-	// Stopped indicates that the process has ran and exited
39
-	StatusStopped Status = "stopped"
40
-	// Paused indicates that the process is currently paused
41
-	StatusPaused Status = "paused"
42
-	// Pausing indicates that the process is currently switching from a
43
-	// running state into a paused state
44
-	StatusPausing Status = "pausing"
45
-	// Unknown indicates that we could not determine the status from the runtime
46
-	StatusUnknown Status = "unknown"
47
-)
48
-
49
-// EventInfo contains the event info
50
-type EventInfo struct {
51
-	ContainerID string
52
-	ProcessID   string
53
-	Pid         uint32
54
-	ExitCode    uint32
55
-	ExitedAt    time.Time
56
-	OOMKilled   bool
57
-	Error       error
58
-}
59
-
60
-// Backend defines callbacks that the client of the library needs to implement.
61
-type Backend interface {
62
-	ProcessEvent(containerID string, event EventType, ei EventInfo) error
63
-}
64
-
65
-// Client provides access to containerd features.
66
-type Client interface {
67
-	Version(ctx context.Context) (containerd.Version, error)
68
-
69
-	Restore(ctx context.Context, containerID string, attachStdio StdioCallback) (alive bool, pid int, err error)
70
-
71
-	Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
72
-	Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio StdioCallback) (pid int, err error)
73
-	SignalProcess(ctx context.Context, containerID, processID string, signal int) error
74
-	Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error)
75
-	ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error
76
-	CloseStdin(ctx context.Context, containerID, processID string) error
77
-	Pause(ctx context.Context, containerID string) error
78
-	Resume(ctx context.Context, containerID string) error
79
-	Stats(ctx context.Context, containerID string) (*Stats, error)
80
-	ListPids(ctx context.Context, containerID string) ([]uint32, error)
81
-	Summary(ctx context.Context, containerID string) ([]Summary, error)
82
-	DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error)
83
-	Delete(ctx context.Context, containerID string) error
84
-	Status(ctx context.Context, containerID string) (Status, error)
85
-
86
-	UpdateResources(ctx context.Context, containerID string, resources *Resources) error
87
-	CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error
88
-}
89
-
90
-// StdioCallback is called to connect a container or process stdio.
91
-type StdioCallback func(io *cio.DirectIO) (cio.IO, error)
92 1
new file mode 100644
... ...
@@ -0,0 +1,94 @@
0
+package types // import "github.com/docker/docker/libcontainerd/types"
1
+
2
+import (
3
+	"context"
4
+	"time"
5
+
6
+	"github.com/containerd/containerd"
7
+	"github.com/containerd/containerd/cio"
8
+	"github.com/opencontainers/runtime-spec/specs-go"
9
+)
10
+
11
+// EventType represents a possible event from libcontainerd
12
+type EventType string
13
+
14
+// Event constants used when reporting events
15
+const (
16
+	EventUnknown     EventType = "unknown"
17
+	EventExit        EventType = "exit"
18
+	EventOOM         EventType = "oom"
19
+	EventCreate      EventType = "create"
20
+	EventStart       EventType = "start"
21
+	EventExecAdded   EventType = "exec-added"
22
+	EventExecStarted EventType = "exec-started"
23
+	EventPaused      EventType = "paused"
24
+	EventResumed     EventType = "resumed"
25
+)
26
+
27
+// Status represents the current status of a container
28
+type Status string
29
+
30
+// Possible container statuses
31
+const (
32
+	// Running indicates the process is currently executing
33
+	StatusRunning Status = "running"
34
+	// Created indicates the process has been created within containerd but the
35
+	// user's defined process has not started
36
+	StatusCreated Status = "created"
37
+	// Stopped indicates that the process has ran and exited
38
+	StatusStopped Status = "stopped"
39
+	// Paused indicates that the process is currently paused
40
+	StatusPaused Status = "paused"
41
+	// Pausing indicates that the process is currently switching from a
42
+	// running state into a paused state
43
+	StatusPausing Status = "pausing"
44
+	// Unknown indicates that we could not determine the status from the runtime
45
+	StatusUnknown Status = "unknown"
46
+)
47
+
48
+// EventInfo contains the event info
49
+type EventInfo struct {
50
+	ContainerID string
51
+	ProcessID   string
52
+	Pid         uint32
53
+	ExitCode    uint32
54
+	ExitedAt    time.Time
55
+	OOMKilled   bool
56
+	Error       error
57
+}
58
+
59
+// Backend defines callbacks that the client of the library needs to implement.
60
+type Backend interface {
61
+	ProcessEvent(containerID string, event EventType, ei EventInfo) error
62
+}
63
+
64
+// Client provides access to containerd features.
65
+type Client interface {
66
+	Version(ctx context.Context) (containerd.Version, error)
67
+
68
+	Restore(ctx context.Context, containerID string, attachStdio StdioCallback) (alive bool, pid int, err error)
69
+
70
+	Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
71
+	Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio StdioCallback) (pid int, err error)
72
+	SignalProcess(ctx context.Context, containerID, processID string, signal int) error
73
+	Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error)
74
+	ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error
75
+	CloseStdin(ctx context.Context, containerID, processID string) error
76
+	Pause(ctx context.Context, containerID string) error
77
+	Resume(ctx context.Context, containerID string) error
78
+	Stats(ctx context.Context, containerID string) (*Stats, error)
79
+	ListPids(ctx context.Context, containerID string) ([]uint32, error)
80
+	Summary(ctx context.Context, containerID string) ([]Summary, error)
81
+	DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error)
82
+	Delete(ctx context.Context, containerID string) error
83
+	Status(ctx context.Context, containerID string) (Status, error)
84
+
85
+	UpdateResources(ctx context.Context, containerID string, resources *Resources) error
86
+	CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error
87
+}
88
+
89
+// StdioCallback is called to connect a container or process stdio.
90
+type StdioCallback func(io *cio.DirectIO) (cio.IO, error)
91
+
92
+// InitProcessName is the name given to the first process of a container
93
+const InitProcessName = "init"
0 94
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package types // import "github.com/docker/docker/libcontainerd/types"
1
+
2
+import (
3
+	"time"
4
+
5
+	"github.com/containerd/cgroups"
6
+	"github.com/opencontainers/runtime-spec/specs-go"
7
+)
8
+
9
+// Summary is not used on linux
10
+type Summary struct{}
11
+
12
+// Stats holds metrics properties as returned by containerd
13
+type Stats struct {
14
+	Read    time.Time
15
+	Metrics *cgroups.Metrics
16
+}
17
+
18
+// InterfaceToStats returns a stats object from the platform-specific interface.
19
+func InterfaceToStats(read time.Time, v interface{}) *Stats {
20
+	return &Stats{
21
+		Metrics: v.(*cgroups.Metrics),
22
+		Read:    read,
23
+	}
24
+}
25
+
26
+// Resources defines updatable container resource values. TODO: it must match containerd upcoming API
27
+type Resources specs.LinuxResources
28
+
29
+// Checkpoints contains the details of a checkpoint
30
+type Checkpoints struct{}
0 31
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package types // import "github.com/docker/docker/libcontainerd/types"
1
+
2
+import (
3
+	"time"
4
+
5
+	"github.com/Microsoft/hcsshim"
6
+)
7
+
8
+// Summary contains a ProcessList item from HCS to support `top`
9
+type Summary hcsshim.ProcessListItem
10
+
11
+// Stats contains statistics from HCS
12
+type Stats struct {
13
+	Read     time.Time
14
+	HCSStats *hcsshim.Statistics
15
+}
16
+
17
+// InterfaceToStats returns a stats object from the platform-specific interface.
18
+func InterfaceToStats(read time.Time, v interface{}) *Stats {
19
+	return &Stats{
20
+		HCSStats: v.(*hcsshim.Statistics),
21
+		Read:     read,
22
+	}
23
+}
24
+
25
+// Resources defines updatable container resource values.
26
+type Resources struct{}
27
+
28
+// Checkpoint holds the details of a checkpoint (not supported in windows)
29
+type Checkpoint struct {
30
+	Name string
31
+}
32
+
33
+// Checkpoints contains the details of a checkpoint
34
+type Checkpoints struct {
35
+	Checkpoints []*Checkpoint
36
+}
0 37
deleted file mode 100644
... ...
@@ -1,30 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"time"
5
-
6
-	"github.com/containerd/cgroups"
7
-	"github.com/opencontainers/runtime-spec/specs-go"
8
-)
9
-
10
-// Summary is not used on linux
11
-type Summary struct{}
12
-
13
-// Stats holds metrics properties as returned by containerd
14
-type Stats struct {
15
-	Read    time.Time
16
-	Metrics *cgroups.Metrics
17
-}
18
-
19
-func interfaceToStats(read time.Time, v interface{}) *Stats {
20
-	return &Stats{
21
-		Metrics: v.(*cgroups.Metrics),
22
-		Read:    read,
23
-	}
24
-}
25
-
26
-// Resources defines updatable container resource values. TODO: it must match containerd upcoming API
27
-type Resources specs.LinuxResources
28
-
29
-// Checkpoints contains the details of a checkpoint
30
-type Checkpoints struct{}
31 1
deleted file mode 100644
... ...
@@ -1,42 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"time"
5
-
6
-	"github.com/Microsoft/hcsshim"
7
-	opengcs "github.com/Microsoft/opengcs/client"
8
-)
9
-
10
-// Summary contains a ProcessList item from HCS to support `top`
11
-type Summary hcsshim.ProcessListItem
12
-
13
-// Stats contains statistics from HCS
14
-type Stats struct {
15
-	Read     time.Time
16
-	HCSStats *hcsshim.Statistics
17
-}
18
-
19
-func interfaceToStats(read time.Time, v interface{}) *Stats {
20
-	return &Stats{
21
-		HCSStats: v.(*hcsshim.Statistics),
22
-		Read:     read,
23
-	}
24
-}
25
-
26
-// Resources defines updatable container resource values.
27
-type Resources struct{}
28
-
29
-// LCOWOption is a CreateOption required for LCOW configuration
30
-type LCOWOption struct {
31
-	Config *opengcs.Config
32
-}
33
-
34
-// Checkpoint holds the details of a checkpoint (not supported in windows)
35
-type Checkpoint struct {
36
-	Name string
37
-}
38
-
39
-// Checkpoints contains the details of a checkpoint
40
-type Checkpoints struct {
41
-	Checkpoints []*Checkpoint
42
-}
43 1
deleted file mode 100644
... ...
@@ -1,38 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"strings"
5
-
6
-	opengcs "github.com/Microsoft/opengcs/client"
7
-)
8
-
9
-// setupEnvironmentVariables converts a string array of environment variables
10
-// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
11
-func setupEnvironmentVariables(a []string) map[string]string {
12
-	r := make(map[string]string)
13
-	for _, s := range a {
14
-		arr := strings.SplitN(s, "=", 2)
15
-		if len(arr) == 2 {
16
-			r[arr[0]] = arr[1]
17
-		}
18
-	}
19
-	return r
20
-}
21
-
22
-// Apply for the LCOW option is a no-op.
23
-func (s *LCOWOption) Apply(interface{}) error {
24
-	return nil
25
-}
26
-
27
-// debugGCS is a dirty hack for debugging for Linux Utility VMs. It simply
28
-// runs a bunch of commands inside the UVM, but seriously aides in advanced debugging.
29
-func (c *container) debugGCS() {
30
-	if c == nil || c.isWindows || c.hcsContainer == nil {
31
-		return
32
-	}
33
-	cfg := opengcs.Config{
34
-		Uvm:               c.hcsContainer,
35
-		UvmTimeoutSeconds: 600,
36
-	}
37
-	cfg.DebugGCS()
38
-}
39 1
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-package libcontainerd // import "github.com/docker/docker/libcontainerd"
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-func TestEnvironmentParsing(t *testing.T) {
8
-	env := []string{"foo=bar", "car=hat", "a=b=c"}
9
-	result := setupEnvironmentVariables(env)
10
-	if len(result) != 3 || result["foo"] != "bar" || result["car"] != "hat" || result["a"] != "b=c" {
11
-		t.Fatalf("Expected map[foo:bar car:hat a:b=c], got %v", result)
12
-	}
13
-}
... ...
@@ -5,3 +5,8 @@ package system // import "github.com/docker/docker/pkg/system"
5 5
 // InitLCOW does nothing since LCOW is a windows only feature
6 6
 func InitLCOW(experimental bool) {
7 7
 }
8
+
9
+// ContainerdRuntimeSupported returns true if the use of ContainerD runtime is supported.
10
+func ContainerdRuntimeSupported(_ bool, _ string) bool {
11
+	return true
12
+}
... ...
@@ -1,7 +1,19 @@
1 1
 package system // import "github.com/docker/docker/pkg/system"
2 2
 
3
-// lcowSupported determines if Linux Containers on Windows are supported.
4
-var lcowSupported = false
3
+import (
4
+	"os"
5
+
6
+	"github.com/sirupsen/logrus"
7
+)
8
+
9
+var (
10
+	// lcowSupported determines if Linux Containers on Windows are supported.
11
+	lcowSupported = false
12
+
13
+	// containerdRuntimeSupported determines if ContainerD should be the runtime.
14
+	// As of March 2019, this is an experimental feature.
15
+	containerdRuntimeSupported = false
16
+)
5 17
 
6 18
 // InitLCOW sets whether LCOW is supported or not
7 19
 func InitLCOW(experimental bool) {
... ...
@@ -10,3 +22,19 @@ func InitLCOW(experimental bool) {
10 10
 		lcowSupported = true
11 11
 	}
12 12
 }
13
+
14
+// InitContainerdRuntime sets whether to use ContainerD for runtime
15
+// on Windows. This is an experimental feature still in development, and
16
+// also requires an environment variable to be set (so as not to turn the
17
+// feature on from simply experimental which would also mean LCOW.
18
+func InitContainerdRuntime(experimental bool, cdPath string) {
19
+	if experimental && len(cdPath) > 0 && len(os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME")) > 0 {
20
+		logrus.Warnf("Using ContainerD runtime. This feature is experimental")
21
+		containerdRuntimeSupported = true
22
+	}
23
+}
24
+
25
+// ContainerdRuntimeSupported returns true if the use of ContainerD runtime is supported.
26
+func ContainerdRuntimeSupported() bool {
27
+	return containerdRuntimeSupported
28
+}
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"github.com/containerd/containerd/runtime/linux/runctypes"
13 13
 	"github.com/docker/docker/errdefs"
14 14
 	"github.com/docker/docker/libcontainerd"
15
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
15 16
 	"github.com/opencontainers/runtime-spec/specs-go"
16 17
 	"github.com/pkg/errors"
17 18
 	"github.com/sirupsen/logrus"
... ...
@@ -30,11 +31,11 @@ type ExitHandler interface {
30 30
 // However right now this whole package is tied to github.com/docker/docker/libcontainerd
31 31
 type Client interface {
32 32
 	Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
33
-	Restore(ctx context.Context, containerID string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error)
34
-	Status(ctx context.Context, containerID string) (libcontainerd.Status, error)
33
+	Restore(ctx context.Context, containerID string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error)
34
+	Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error)
35 35
 	Delete(ctx context.Context, containerID string) error
36 36
 	DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error)
37
-	Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error)
37
+	Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error)
38 38
 	SignalProcess(ctx context.Context, containerID, processID string, signal int) error
39 39
 }
40 40
 
... ...
@@ -87,7 +88,7 @@ func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteClo
87 87
 				logrus.WithError(err2).WithField("id", id).Warn("Received an error while attempting to read plugin status")
88 88
 			}
89 89
 		} else {
90
-			if status != libcontainerd.StatusRunning && status != libcontainerd.StatusUnknown {
90
+			if status != libcontainerdtypes.StatusRunning && status != libcontainerdtypes.StatusUnknown {
91 91
 				if err2 := e.client.Delete(ctx, id); err2 != nil && !errdefs.IsNotFound(err2) {
92 92
 					logrus.WithError(err2).WithField("plugin", id).Error("Error cleaning up containerd container")
93 93
 				}
... ...
@@ -122,19 +123,19 @@ func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, erro
122 122
 // IsRunning returns if the container with the given id is running
123 123
 func (e *Executor) IsRunning(id string) (bool, error) {
124 124
 	status, err := e.client.Status(context.Background(), id)
125
-	return status == libcontainerd.StatusRunning, err
125
+	return status == libcontainerdtypes.StatusRunning, err
126 126
 }
127 127
 
128 128
 // Signal sends the specified signal to the container
129 129
 func (e *Executor) Signal(id string, signal int) error {
130
-	return e.client.SignalProcess(context.Background(), id, libcontainerd.InitProcessName, signal)
130
+	return e.client.SignalProcess(context.Background(), id, libcontainerdtypes.InitProcessName, signal)
131 131
 }
132 132
 
133 133
 // ProcessEvent handles events from containerd
134 134
 // All events are ignored except the exit event, which is sent of to the stored handler
135
-func (e *Executor) ProcessEvent(id string, et libcontainerd.EventType, ei libcontainerd.EventInfo) error {
135
+func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
136 136
 	switch et {
137
-	case libcontainerd.EventExit:
137
+	case libcontainerdtypes.EventExit:
138 138
 		deleteTaskAndContainer(context.Background(), e.client, id)
139 139
 		return e.exitHandler.HandleExitEvent(ei.ContainerID)
140 140
 	}
... ...
@@ -152,7 +153,7 @@ func (c *rio) Wait() {
152 152
 	c.IO.Wait()
153 153
 }
154 154
 
155
-func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerd.StdioCallback {
155
+func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
156 156
 	return func(iop *cio.DirectIO) (cio.IO, error) {
157 157
 		if iop.Stdin != nil {
158 158
 			iop.Stdin.Close()
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"testing"
9 9
 	"time"
10 10
 
11
-	"github.com/docker/docker/libcontainerd"
11
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
12 12
 	"github.com/opencontainers/runtime-spec/specs-go"
13 13
 	"github.com/pkg/errors"
14 14
 	"gotest.tools/assert"
... ...
@@ -82,22 +82,22 @@ func (c *mockClient) Create(ctx context.Context, id string, _ *specs.Spec, _ int
82 82
 	return nil
83 83
 }
84 84
 
85
-func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) {
85
+func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
86 86
 	return false, 0, nil
87 87
 }
88 88
 
89
-func (c *mockClient) Status(ctx context.Context, id string) (libcontainerd.Status, error) {
89
+func (c *mockClient) Status(ctx context.Context, id string) (libcontainerdtypes.Status, error) {
90 90
 	c.mu.Lock()
91 91
 	defer c.mu.Unlock()
92 92
 
93 93
 	running, ok := c.containers[id]
94 94
 	if !ok {
95
-		return libcontainerd.StatusUnknown, errors.New("not found")
95
+		return libcontainerdtypes.StatusUnknown, errors.New("not found")
96 96
 	}
97 97
 	if running {
98
-		return libcontainerd.StatusRunning, nil
98
+		return libcontainerdtypes.StatusRunning, nil
99 99
 	}
100
-	return libcontainerd.StatusStopped, nil
100
+	return libcontainerdtypes.StatusStopped, nil
101 101
 }
102 102
 
103 103
 func (c *mockClient) Delete(ctx context.Context, id string) error {
... ...
@@ -111,7 +111,7 @@ func (c *mockClient) DeleteTask(ctx context.Context, id string) (uint32, time.Ti
111 111
 	return 0, time.Time{}, nil
112 112
 }
113 113
 
114
-func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) {
114
+func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error) {
115 115
 	c.mu.Lock()
116 116
 	defer c.mu.Unlock()
117 117