Browse code

Move libcontainerd/local to daemon/internal/libcontainerd/local

Signed-off-by: Derek McGowan <derek@mcg.dev>

Derek McGowan authored on 2025/06/28 06:27:36
Showing 10 changed files
... ...
@@ -16,8 +16,8 @@ import (
16 16
 	networktypes "github.com/docker/docker/api/types/network"
17 17
 	"github.com/docker/docker/daemon/config"
18 18
 	"github.com/docker/docker/daemon/container"
19
+	"github.com/docker/docker/daemon/internal/libcontainerd/local"
19 20
 	"github.com/docker/docker/daemon/network"
20
-	"github.com/docker/docker/libcontainerd/local"
21 21
 	"github.com/docker/docker/libcontainerd/remote"
22 22
 	"github.com/docker/docker/libnetwork"
23 23
 	nwconfig "github.com/docker/docker/libnetwork/config"
24 24
new file mode 100644
... ...
@@ -0,0 +1,1225 @@
0
+package 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
+	"fmt"
8
+	"io"
9
+	"os"
10
+	"path/filepath"
11
+	"regexp"
12
+	"runtime"
13
+	"strings"
14
+	"sync"
15
+	"syscall"
16
+	"time"
17
+
18
+	"github.com/Microsoft/hcsshim"
19
+	containerd "github.com/containerd/containerd/v2/client"
20
+	"github.com/containerd/containerd/v2/pkg/cio"
21
+	cerrdefs "github.com/containerd/errdefs"
22
+	"github.com/containerd/log"
23
+	"github.com/docker/docker/errdefs"
24
+	"github.com/docker/docker/libcontainerd/queue"
25
+	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
26
+	"github.com/docker/docker/pkg/system"
27
+	"github.com/opencontainers/runtime-spec/specs-go"
28
+	"github.com/pkg/errors"
29
+	"golang.org/x/sys/windows"
30
+	"google.golang.org/protobuf/types/known/timestamppb"
31
+)
32
+
33
+type process struct {
34
+	// mu guards the mutable fields of this struct.
35
+	//
36
+	// Always lock mu before ctr's mutex to prevent deadlocks.
37
+	mu         sync.Mutex
38
+	id         string                 // Invariants: immutable
39
+	ctr        *container             // Invariants: immutable, ctr != nil
40
+	hcsProcess hcsshim.Process        // Is set to nil on process exit
41
+	exited     *containerd.ExitStatus // Valid iff waitCh is closed
42
+	waitCh     chan struct{}
43
+}
44
+
45
+type task struct {
46
+	process
47
+}
48
+
49
+type container struct {
50
+	mu sync.Mutex
51
+
52
+	// The ociSpec is required, as client.Create() needs a spec, but can
53
+	// be called from the RestartManager context which does not otherwise
54
+	// have access to the Spec
55
+	//
56
+	// A container value with ociSpec == nil represents a container which
57
+	// has been loaded with (*client).LoadContainer, and is ineligible to
58
+	// be Start()ed.
59
+	ociSpec *specs.Spec
60
+
61
+	hcsContainer hcsshim.Container // Is set to nil on container delete
62
+	isPaused     bool
63
+
64
+	client           *client
65
+	id               string
66
+	terminateInvoked bool
67
+
68
+	// task is a reference to the current task for the container. As a
69
+	// corollary, when task == nil the container has no current task: the
70
+	// container was never Start()ed or the task was Delete()d.
71
+	task *task
72
+}
73
+
74
+// defaultOwner is a tag passed to HCS to allow it to differentiate between
75
+// container creator management stacks. We hard code "docker" in the case
76
+// of docker.
77
+const defaultOwner = "docker"
78
+
79
+type client struct {
80
+	backend libcontainerdtypes.Backend
81
+	logger  *log.Entry
82
+	eventQ  queue.Queue
83
+}
84
+
85
+// NewClient creates a new local executor for windows
86
+func NewClient(ctx context.Context, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
87
+	return &client{
88
+		backend: b,
89
+		logger:  log.G(ctx).WithField("module", "libcontainerd"),
90
+	}, nil
91
+}
92
+
93
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
94
+	return containerd.Version{}, errors.New("not implemented on Windows")
95
+}
96
+
97
+// NewContainer is the entrypoint to create a container from a spec.
98
+// Table below shows the fields required for HCS JSON calling parameters,
99
+// where if not populated, is omitted.
100
+// +-----------------+--------------------------------------------+---------------------------------------------------+
101
+// |                 | Isolation=Process                          | Isolation=Hyper-V                                 |
102
+// +-----------------+--------------------------------------------+---------------------------------------------------+
103
+// | VolumePath      | \\?\\Volume{GUIDa}                         |                                                   |
104
+// | LayerFolderPath | %root%\windowsfilter\containerID           |                                                   |
105
+// | Layers[]        | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID        |
106
+// | HvRuntime       |                                            | ImagePath=%root%\BaseLayerID\UtilityVM            |
107
+// +-----------------+--------------------------------------------+---------------------------------------------------+
108
+//
109
+// Isolation=Process example:
110
+//
111
+//	{
112
+//		"SystemType": "Container",
113
+//		"Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
114
+//		"Owner": "docker",
115
+//		"VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
116
+//		"IgnoreFlushesDuringBoot": true,
117
+//		"LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
118
+//		"Layers": [{
119
+//			"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
120
+//			"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
121
+//		}],
122
+//		"HostName": "5e0055c814a6",
123
+//		"MappedDirectories": [],
124
+//		"HvPartition": false,
125
+//		"EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
126
+//	}
127
+//
128
+// Isolation=Hyper-V example:
129
+//
130
+//	{
131
+//		"SystemType": "Container",
132
+//		"Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
133
+//		"Owner": "docker",
134
+//		"IgnoreFlushesDuringBoot": true,
135
+//		"Layers": [{
136
+//			"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
137
+//			"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
138
+//		}],
139
+//		"HostName": "475c2c58933b",
140
+//		"MappedDirectories": [],
141
+//		"HvPartition": true,
142
+//		"EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
143
+//		"DNSSearchList": "a.com,b.com,c.com",
144
+//		"HvRuntime": {
145
+//			"ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
146
+//		},
147
+//	}
148
+func (c *client) NewContainer(_ context.Context, id string, spec *specs.Spec, _ string, _ interface{}, _ ...containerd.NewContainerOpts) (libcontainerdtypes.Container, error) {
149
+	if spec.Linux != nil {
150
+		return nil, errors.New("linux containers are not supported on this platform")
151
+	}
152
+	ctr, err := c.createWindows(id, spec)
153
+	if err != nil {
154
+		return nil, err
155
+	}
156
+
157
+	c.eventQ.Append(id, func() {
158
+		c.logger.WithFields(log.Fields{
159
+			"container": id,
160
+			"event":     libcontainerdtypes.EventCreate,
161
+		}).Info("sending event")
162
+
163
+		err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, libcontainerdtypes.EventInfo{
164
+			ContainerID: id,
165
+		})
166
+		if err != nil {
167
+			c.logger.WithFields(log.Fields{
168
+				"container": id,
169
+				"event":     libcontainerdtypes.EventCreate,
170
+				"error":     err,
171
+			}).Error("failed to process event")
172
+		}
173
+	})
174
+	return ctr, nil
175
+}
176
+
177
+func (c *client) createWindows(id string, spec *specs.Spec) (*container, error) {
178
+	logger := c.logger.WithField("container", id)
179
+	configuration := &hcsshim.ContainerConfig{
180
+		SystemType:              "Container",
181
+		Name:                    id,
182
+		Owner:                   defaultOwner,
183
+		IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
184
+		HostName:                spec.Hostname,
185
+		HvPartition:             false,
186
+	}
187
+
188
+	c.extractResourcesFromSpec(spec, configuration)
189
+
190
+	if spec.Windows.Resources != nil {
191
+		if spec.Windows.Resources.Storage != nil {
192
+			if spec.Windows.Resources.Storage.Bps != nil {
193
+				configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
194
+			}
195
+			if spec.Windows.Resources.Storage.Iops != nil {
196
+				configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
197
+			}
198
+		}
199
+	}
200
+
201
+	if spec.Windows.HyperV != nil {
202
+		configuration.HvPartition = true
203
+	}
204
+
205
+	if spec.Windows.Network != nil {
206
+		configuration.EndpointList = spec.Windows.Network.EndpointList
207
+		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
208
+		if spec.Windows.Network.DNSSearchList != nil {
209
+			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
210
+		}
211
+		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
212
+	}
213
+
214
+	if cs, ok := spec.Windows.CredentialSpec.(string); ok {
215
+		configuration.Credentials = cs
216
+	}
217
+
218
+	// We must have least two layers in the spec, the bottom one being a
219
+	// base image, the top one being the RW layer.
220
+	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
221
+		return nil, fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
222
+	}
223
+
224
+	// Strip off the top-most layer as that's passed in separately to HCS
225
+	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
226
+	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
227
+
228
+	if configuration.HvPartition {
229
+		// We don't currently support setting the utility VM image explicitly.
230
+		// TODO circa RS5, this may be re-locatable.
231
+		if spec.Windows.HyperV.UtilityVMPath != "" {
232
+			return nil, errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
233
+		}
234
+
235
+		// Find the upper-most utility VM image.
236
+		var uvmImagePath string
237
+		for _, path := range layerFolders {
238
+			fullPath := filepath.Join(path, "UtilityVM")
239
+			_, err := os.Stat(fullPath)
240
+			if err == nil {
241
+				uvmImagePath = fullPath
242
+				break
243
+			}
244
+			if !os.IsNotExist(err) {
245
+				return nil, err
246
+			}
247
+		}
248
+		if uvmImagePath == "" {
249
+			return nil, errors.New("utility VM image could not be found")
250
+		}
251
+		configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
252
+
253
+		if spec.Root.Path != "" {
254
+			return nil, errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
255
+		}
256
+	} else {
257
+		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}\}\\$`
258
+		if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
259
+			return nil, fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
260
+		}
261
+		// HCS API requires the trailing backslash to be removed
262
+		configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
263
+	}
264
+
265
+	if spec.Root.Readonly {
266
+		return nil, errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
267
+	}
268
+
269
+	for _, layerPath := range layerFolders {
270
+		_, filename := filepath.Split(layerPath)
271
+		g, err := hcsshim.NameToGuid(filename)
272
+		if err != nil {
273
+			return nil, err
274
+		}
275
+		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
276
+			ID:   g.ToString(),
277
+			Path: layerPath,
278
+		})
279
+	}
280
+
281
+	// Add the mounts (volumes, bind mounts etc) to the structure
282
+	var mds []hcsshim.MappedDir
283
+	var mps []hcsshim.MappedPipe
284
+	for _, mount := range spec.Mounts {
285
+		const pipePrefix = `\\.\pipe\`
286
+		if mount.Type != "" {
287
+			return nil, fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
288
+		}
289
+		if strings.HasPrefix(mount.Destination, pipePrefix) {
290
+			mp := hcsshim.MappedPipe{
291
+				HostPath:          mount.Source,
292
+				ContainerPipeName: mount.Destination[len(pipePrefix):],
293
+			}
294
+			mps = append(mps, mp)
295
+		} else {
296
+			md := hcsshim.MappedDir{
297
+				HostPath:      mount.Source,
298
+				ContainerPath: mount.Destination,
299
+				ReadOnly:      false,
300
+			}
301
+			for _, o := range mount.Options {
302
+				if strings.ToLower(o) == "ro" {
303
+					md.ReadOnly = true
304
+				}
305
+			}
306
+			mds = append(mds, md)
307
+		}
308
+	}
309
+	configuration.MappedDirectories = mds
310
+	configuration.MappedPipes = mps
311
+
312
+	if len(spec.Windows.Devices) > 0 {
313
+		// Add any device assignments
314
+		if configuration.HvPartition {
315
+			return nil, errors.New("device assignment is not supported for HyperV containers")
316
+		}
317
+		for _, d := range spec.Windows.Devices {
318
+			// Per https://github.com/microsoft/hcsshim/blob/v0.9.2/internal/uvm/virtual_device.go#L17-L18,
319
+			// these represent an Interface Class GUID.
320
+			if d.IDType != "class" && d.IDType != "vpci-class-guid" {
321
+				return nil, errors.Errorf("device assignment of type '%s' is not supported", d.IDType)
322
+			}
323
+			configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
324
+		}
325
+	}
326
+
327
+	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
328
+	if err != nil {
329
+		return nil, err
330
+	}
331
+
332
+	// Construct a container object for calling start on it.
333
+	ctr := &container{
334
+		client:       c,
335
+		id:           id,
336
+		ociSpec:      spec,
337
+		hcsContainer: hcsContainer,
338
+	}
339
+
340
+	logger.Debug("starting container")
341
+	if err := ctr.hcsContainer.Start(); err != nil {
342
+		logger.WithError(err).Error("failed to start container")
343
+		ctr.mu.Lock()
344
+		if err := ctr.terminateContainer(); err != nil {
345
+			logger.WithError(err).Error("failed to cleanup after a failed Start")
346
+		} else {
347
+			logger.Debug("cleaned up after failed Start by calling Terminate")
348
+		}
349
+		ctr.mu.Unlock()
350
+		return nil, err
351
+	}
352
+
353
+	logger.Debug("createWindows() completed successfully")
354
+	return ctr, nil
355
+}
356
+
357
+func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
358
+	if spec.Windows.Resources == nil {
359
+		return
360
+	}
361
+	if spec.Windows.Resources.CPU != nil {
362
+		if spec.Windows.Resources.CPU.Count != nil {
363
+			// This check is being done here rather than in adaptContainerSettings
364
+			// because we don't want to update the HostConfig in case this container
365
+			// is moved to a host with more CPUs than this one.
366
+			cpuCount := *spec.Windows.Resources.CPU.Count
367
+			hostCPUCount := uint64(runtime.NumCPU())
368
+			if cpuCount > hostCPUCount {
369
+				c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
370
+				cpuCount = hostCPUCount
371
+			}
372
+			configuration.ProcessorCount = uint32(cpuCount)
373
+		}
374
+		if spec.Windows.Resources.CPU.Shares != nil {
375
+			configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
376
+		}
377
+		if spec.Windows.Resources.CPU.Maximum != nil {
378
+			configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum)
379
+		}
380
+	}
381
+	if spec.Windows.Resources.Memory != nil {
382
+		if spec.Windows.Resources.Memory.Limit != nil {
383
+			configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
384
+		}
385
+	}
386
+}
387
+
388
+func (ctr *container) NewTask(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
389
+	ctr.mu.Lock()
390
+	defer ctr.mu.Unlock()
391
+
392
+	switch {
393
+	case ctr.ociSpec == nil:
394
+		return nil, errors.WithStack(errdefs.NotImplemented(errors.New("a restored container cannot be started")))
395
+	case ctr.task != nil:
396
+		return nil, errors.WithStack(errdefs.NotModified(cerrdefs.ErrAlreadyExists))
397
+	}
398
+
399
+	logger := ctr.client.logger.WithField("container", ctr.id)
400
+
401
+	// Note we always tell HCS to create stdout as it's required
402
+	// regardless of '-i' or '-t' options, so that docker can always grab
403
+	// the output through logs. We also tell HCS to always create stdin,
404
+	// even if it's not used - it will be closed shortly. Stderr is only
405
+	// created if it we're not -t.
406
+	var (
407
+		emulateConsole   bool
408
+		createStdErrPipe bool
409
+	)
410
+	if ctr.ociSpec.Process != nil {
411
+		emulateConsole = ctr.ociSpec.Process.Terminal
412
+		createStdErrPipe = !ctr.ociSpec.Process.Terminal
413
+	}
414
+
415
+	createProcessParms := &hcsshim.ProcessConfig{
416
+		EmulateConsole:   emulateConsole,
417
+		WorkingDirectory: ctr.ociSpec.Process.Cwd,
418
+		CreateStdInPipe:  true,
419
+		CreateStdOutPipe: true,
420
+		CreateStdErrPipe: createStdErrPipe,
421
+	}
422
+
423
+	if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
424
+		createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
425
+		createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
426
+	}
427
+
428
+	// Configure the environment for the process
429
+	createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
430
+
431
+	// Configure the CommandLine/CommandArgs
432
+	setCommandLineAndArgs(ctr.ociSpec.Process, createProcessParms)
433
+	logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
434
+
435
+	createProcessParms.User = ctr.ociSpec.Process.User.Username
436
+
437
+	// Start the command running in the container.
438
+	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
439
+	if err != nil {
440
+		logger.WithError(err).Error("CreateProcess() failed")
441
+		return nil, err
442
+	}
443
+
444
+	defer func() {
445
+		if retErr != nil {
446
+			if err := newProcess.Kill(); err != nil {
447
+				logger.WithError(err).Error("failed to kill process")
448
+			}
449
+			go func() {
450
+				if err := newProcess.Wait(); err != nil {
451
+					logger.WithError(err).Error("failed to wait for process")
452
+				}
453
+				if err := newProcess.Close(); err != nil {
454
+					logger.WithError(err).Error("failed to clean process resources")
455
+				}
456
+			}()
457
+		}
458
+	}()
459
+
460
+	pid := newProcess.Pid()
461
+	logger.WithField("pid", pid).Debug("init process started")
462
+
463
+	dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal)
464
+	if err != nil {
465
+		logger.WithError(err).Error("failed to get stdio pipes")
466
+		return nil, err
467
+	}
468
+	_, err = attachStdio(dio)
469
+	if err != nil {
470
+		logger.WithError(err).Error("failed to attach stdio")
471
+		return nil, err
472
+	}
473
+
474
+	t := &task{process{
475
+		id:         ctr.id,
476
+		ctr:        ctr,
477
+		hcsProcess: newProcess,
478
+		waitCh:     make(chan struct{}),
479
+	}}
480
+
481
+	// All fallible operations have succeeded so it is now safe to set the
482
+	// container's current task.
483
+	ctr.task = t
484
+
485
+	// Spin up a goroutine to notify the backend and clean up resources when
486
+	// the task exits. Defer until after the start event is sent so that the
487
+	// exit event is not sent out-of-order.
488
+	defer func() { go t.reap() }()
489
+
490
+	// Generate the associated event
491
+	ctr.client.eventQ.Append(ctr.id, func() {
492
+		ei := libcontainerdtypes.EventInfo{
493
+			ContainerID: ctr.id,
494
+			ProcessID:   t.id,
495
+			Pid:         uint32(pid),
496
+		}
497
+		ctr.client.logger.WithFields(log.Fields{
498
+			"container":  ctr.id,
499
+			"event":      libcontainerdtypes.EventStart,
500
+			"event-info": ei,
501
+		}).Info("sending event")
502
+		err := ctr.client.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei)
503
+		if err != nil {
504
+			ctr.client.logger.WithError(err).WithFields(log.Fields{
505
+				"container":  ei.ContainerID,
506
+				"event":      libcontainerdtypes.EventStart,
507
+				"event-info": ei,
508
+			}).Error("failed to process event")
509
+		}
510
+	})
511
+	logger.Debug("start() completed")
512
+	return t, nil
513
+}
514
+
515
+func (*task) Start(context.Context) error {
516
+	// No-op on Windows.
517
+	return nil
518
+}
519
+
520
+func (ctr *container) Task(context.Context) (libcontainerdtypes.Task, error) {
521
+	ctr.mu.Lock()
522
+	defer ctr.mu.Unlock()
523
+	if ctr.task == nil {
524
+		return nil, errdefs.NotFound(cerrdefs.ErrNotFound)
525
+	}
526
+	return ctr.task, nil
527
+}
528
+
529
+// setCommandLineAndArgs configures the HCS ProcessConfig based on an OCI process spec
530
+func setCommandLineAndArgs(process *specs.Process, createProcessParms *hcsshim.ProcessConfig) {
531
+	if process.CommandLine != "" {
532
+		createProcessParms.CommandLine = process.CommandLine
533
+	} else {
534
+		createProcessParms.CommandLine = system.EscapeArgs(process.Args)
535
+	}
536
+}
537
+
538
+func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
539
+	stdin, stdout, stderr, err := newProcess.Stdio()
540
+	if err != nil {
541
+		return nil, err
542
+	}
543
+
544
+	dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal)
545
+
546
+	// Convert io.ReadClosers to io.Readers
547
+	if stdout != nil {
548
+		dio.Stdout = io.NopCloser(&autoClosingReader{ReadCloser: stdout})
549
+	}
550
+	if stderr != nil {
551
+		dio.Stderr = io.NopCloser(&autoClosingReader{ReadCloser: stderr})
552
+	}
553
+	return dio, nil
554
+}
555
+
556
+// Exec launches a process in a running container.
557
+//
558
+// The processID argument is entirely informational. As there is no mechanism
559
+// (exposed through the libcontainerd interfaces) to enumerate or reference an
560
+// exec'd process by ID, uniqueness is not currently enforced.
561
+func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Process, retErr error) {
562
+	hcsContainer, err := t.getHCSContainer()
563
+	if err != nil {
564
+		return nil, err
565
+	}
566
+	logger := t.ctr.client.logger.WithFields(log.Fields{
567
+		"container": t.ctr.id,
568
+		"exec":      processID,
569
+	})
570
+
571
+	// Note we always tell HCS to
572
+	// create stdout as it's required regardless of '-i' or '-t' options, so that
573
+	// docker can always grab the output through logs. We also tell HCS to always
574
+	// create stdin, even if it's not used - it will be closed shortly. Stderr
575
+	// is only created if it we're not -t.
576
+	createProcessParms := &hcsshim.ProcessConfig{
577
+		CreateStdInPipe:  true,
578
+		CreateStdOutPipe: true,
579
+		CreateStdErrPipe: !spec.Terminal,
580
+	}
581
+	if spec.Terminal {
582
+		createProcessParms.EmulateConsole = true
583
+		if spec.ConsoleSize != nil {
584
+			createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height)
585
+			createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width)
586
+		}
587
+	}
588
+
589
+	// Take working directory from the process to add if it is defined,
590
+	// otherwise take from the first process.
591
+	if spec.Cwd != "" {
592
+		createProcessParms.WorkingDirectory = spec.Cwd
593
+	} else {
594
+		createProcessParms.WorkingDirectory = t.ctr.ociSpec.Process.Cwd
595
+	}
596
+
597
+	// Configure the environment for the process
598
+	createProcessParms.Environment = setupEnvironmentVariables(spec.Env)
599
+
600
+	// Configure the CommandLine/CommandArgs
601
+	setCommandLineAndArgs(spec, createProcessParms)
602
+	logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
603
+
604
+	createProcessParms.User = spec.User.Username
605
+
606
+	// Start the command running in the container.
607
+	newProcess, err := hcsContainer.CreateProcess(createProcessParms)
608
+	if err != nil {
609
+		logger.WithError(err).Errorf("exec's CreateProcess() failed")
610
+		return nil, err
611
+	}
612
+	defer func() {
613
+		if retErr != nil {
614
+			if err := newProcess.Kill(); err != nil {
615
+				logger.WithError(err).Error("failed to kill process")
616
+			}
617
+			go func() {
618
+				if err := newProcess.Wait(); err != nil {
619
+					logger.WithError(err).Error("failed to wait for process")
620
+				}
621
+				if err := newProcess.Close(); err != nil {
622
+					logger.WithError(err).Error("failed to clean process resources")
623
+				}
624
+			}()
625
+		}
626
+	}()
627
+
628
+	dio, err := newIOFromProcess(newProcess, spec.Terminal)
629
+	if err != nil {
630
+		logger.WithError(err).Error("failed to get stdio pipes")
631
+		return nil, err
632
+	}
633
+	// Tell the engine to attach streams back to the client
634
+	_, err = attachStdio(dio)
635
+	if err != nil {
636
+		return nil, err
637
+	}
638
+
639
+	p := &process{
640
+		id:         processID,
641
+		ctr:        t.ctr,
642
+		hcsProcess: newProcess,
643
+		waitCh:     make(chan struct{}),
644
+	}
645
+
646
+	// Spin up a goroutine to notify the backend and clean up resources when
647
+	// the process exits. Defer until after the start event is sent so that
648
+	// the exit event is not sent out-of-order.
649
+	defer func() { go p.reap() }()
650
+
651
+	pid := newProcess.Pid()
652
+	t.ctr.client.eventQ.Append(t.ctr.id, func() {
653
+		ei := libcontainerdtypes.EventInfo{
654
+			ContainerID: t.ctr.id,
655
+			ProcessID:   p.id,
656
+			Pid:         uint32(pid),
657
+		}
658
+		t.ctr.client.logger.WithFields(log.Fields{
659
+			"container":  t.ctr.id,
660
+			"event":      libcontainerdtypes.EventExecAdded,
661
+			"event-info": ei,
662
+		}).Info("sending event")
663
+		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecAdded, ei)
664
+		if err != nil {
665
+			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
666
+				"container":  t.ctr.id,
667
+				"event":      libcontainerdtypes.EventExecAdded,
668
+				"event-info": ei,
669
+			}).Error("failed to process event")
670
+		}
671
+		err = t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecStarted, ei)
672
+		if err != nil {
673
+			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
674
+				"container":  t.ctr.id,
675
+				"event":      libcontainerdtypes.EventExecStarted,
676
+				"event-info": ei,
677
+			}).Error("failed to process event")
678
+		}
679
+	})
680
+
681
+	return p, nil
682
+}
683
+
684
+func (p *process) Pid() uint32 {
685
+	p.mu.Lock()
686
+	hcsProcess := p.hcsProcess
687
+	p.mu.Unlock()
688
+	if hcsProcess == nil {
689
+		return 0
690
+	}
691
+	return uint32(hcsProcess.Pid())
692
+}
693
+
694
+func (p *process) Kill(_ context.Context, signal syscall.Signal) error {
695
+	p.mu.Lock()
696
+	hcsProcess := p.hcsProcess
697
+	p.mu.Unlock()
698
+	if hcsProcess == nil {
699
+		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
700
+	}
701
+	return hcsProcess.Kill()
702
+}
703
+
704
+// Kill handles `docker stop` on Windows. While Linux has support for
705
+// the full range of signals, signals aren't really implemented on Windows.
706
+// We fake supporting regular stop and -9 to force kill.
707
+func (t *task) Kill(_ context.Context, signal syscall.Signal) error {
708
+	hcsContainer, err := t.getHCSContainer()
709
+	if err != nil {
710
+		return err
711
+	}
712
+
713
+	logger := t.ctr.client.logger.WithFields(log.Fields{
714
+		"container": t.ctr.id,
715
+		"process":   t.id,
716
+		"pid":       t.Pid(),
717
+		"signal":    signal,
718
+	})
719
+	logger.Debug("Signal()")
720
+
721
+	var op string
722
+	if signal == syscall.SIGKILL {
723
+		// Terminate the compute system
724
+		t.ctr.mu.Lock()
725
+		t.ctr.terminateInvoked = true
726
+		t.ctr.mu.Unlock()
727
+		op, err = "terminate", hcsContainer.Terminate()
728
+	} else {
729
+		// Shut down the container
730
+		op, err = "shutdown", hcsContainer.Shutdown()
731
+	}
732
+	if err != nil {
733
+		if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
734
+			// ignore errors
735
+			logger.WithError(err).Errorf("failed to %s hccshim container", op)
736
+		}
737
+	}
738
+
739
+	return nil
740
+}
741
+
742
+// Resize handles a CLI event to resize an interactive docker run or docker
743
+// exec window.
744
+func (p *process) Resize(_ context.Context, width, height uint32) error {
745
+	p.mu.Lock()
746
+	hcsProcess := p.hcsProcess
747
+	p.mu.Unlock()
748
+	if hcsProcess == nil {
749
+		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
750
+	}
751
+
752
+	p.ctr.client.logger.WithFields(log.Fields{
753
+		"container": p.ctr.id,
754
+		"process":   p.id,
755
+		"height":    height,
756
+		"width":     width,
757
+		"pid":       hcsProcess.Pid(),
758
+	}).Debug("resizing")
759
+	return hcsProcess.ResizeConsole(uint16(width), uint16(height))
760
+}
761
+
762
+func (p *process) CloseStdin(context.Context) error {
763
+	p.mu.Lock()
764
+	hcsProcess := p.hcsProcess
765
+	p.mu.Unlock()
766
+	if hcsProcess == nil {
767
+		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
768
+	}
769
+
770
+	return hcsProcess.CloseStdin()
771
+}
772
+
773
+// Pause handles pause requests for containers
774
+func (t *task) Pause(_ context.Context) error {
775
+	if t.ctr.ociSpec.Windows.HyperV == nil {
776
+		return errdefs.NotImplemented(errors.WithStack(errors.New("not implemented for containers using process isolation")))
777
+	}
778
+
779
+	t.ctr.mu.Lock()
780
+	defer t.ctr.mu.Unlock()
781
+
782
+	if err := t.assertIsCurrentTask(); err != nil {
783
+		return err
784
+	}
785
+	if t.ctr.hcsContainer == nil {
786
+		return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
787
+	}
788
+	if err := t.ctr.hcsContainer.Pause(); err != nil {
789
+		return err
790
+	}
791
+
792
+	t.ctr.isPaused = true
793
+
794
+	t.ctr.client.eventQ.Append(t.ctr.id, func() {
795
+		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
796
+			ContainerID: t.ctr.id,
797
+			ProcessID:   t.id,
798
+		})
799
+		t.ctr.client.logger.WithFields(log.Fields{
800
+			"container": t.ctr.id,
801
+			"event":     libcontainerdtypes.EventPaused,
802
+		}).Info("sending event")
803
+		if err != nil {
804
+			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
805
+				"container": t.ctr.id,
806
+				"event":     libcontainerdtypes.EventPaused,
807
+			}).Error("failed to process event")
808
+		}
809
+	})
810
+
811
+	return nil
812
+}
813
+
814
+// Resume handles resume requests for containers
815
+func (t *task) Resume(ctx context.Context) error {
816
+	if t.ctr.ociSpec.Windows.HyperV == nil {
817
+		return errors.New("cannot resume Windows Server Containers")
818
+	}
819
+
820
+	t.ctr.mu.Lock()
821
+	defer t.ctr.mu.Unlock()
822
+
823
+	if err := t.assertIsCurrentTask(); err != nil {
824
+		return err
825
+	}
826
+	if t.ctr.hcsContainer == nil {
827
+		return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
828
+	}
829
+	if err := t.ctr.hcsContainer.Resume(); err != nil {
830
+		return err
831
+	}
832
+
833
+	t.ctr.isPaused = false
834
+
835
+	t.ctr.client.eventQ.Append(t.ctr.id, func() {
836
+		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
837
+			ContainerID: t.ctr.id,
838
+			ProcessID:   t.id,
839
+		})
840
+		t.ctr.client.logger.WithFields(log.Fields{
841
+			"container": t.ctr.id,
842
+			"event":     libcontainerdtypes.EventResumed,
843
+		}).Info("sending event")
844
+		if err != nil {
845
+			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
846
+				"container": t.ctr.id,
847
+				"event":     libcontainerdtypes.EventResumed,
848
+			}).Error("failed to process event")
849
+		}
850
+	})
851
+
852
+	return nil
853
+}
854
+
855
+// Stats handles stats requests for containers
856
+func (t *task) Stats(_ context.Context) (*libcontainerdtypes.Stats, error) {
857
+	hc, err := t.getHCSContainer()
858
+	if err != nil {
859
+		return nil, err
860
+	}
861
+
862
+	readAt := time.Now()
863
+	s, err := hc.Statistics()
864
+	if err != nil {
865
+		return nil, err
866
+	}
867
+	return &libcontainerdtypes.Stats{
868
+		Read:     readAt,
869
+		HCSStats: &s,
870
+	}, nil
871
+}
872
+
873
+// LoadContainer is the handler for restoring a container
874
+func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtypes.Container, error) {
875
+	c.logger.WithField("container", id).Debug("LoadContainer()")
876
+
877
+	// TODO Windows: On RS1, a re-attach isn't possible.
878
+	// However, there is a scenario in which there is an issue.
879
+	// Consider a background container. The daemon dies unexpectedly.
880
+	// HCS will still have the compute service alive and running.
881
+	// For consistence, we call in to shoot it regardless if HCS knows about it
882
+	// We explicitly just log a warning if the terminate fails.
883
+	// Then we tell the backend the container exited.
884
+	hc, err := hcsshim.OpenContainer(id)
885
+	if err != nil {
886
+		return nil, errdefs.NotFound(errors.New("container not found"))
887
+	}
888
+	const terminateTimeout = time.Minute * 2
889
+	err = hc.Terminate()
890
+
891
+	if hcsshim.IsPending(err) {
892
+		err = hc.WaitTimeout(terminateTimeout)
893
+	} else if hcsshim.IsAlreadyStopped(err) {
894
+		err = nil
895
+	}
896
+
897
+	if err != nil {
898
+		c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
899
+		return nil, err
900
+	}
901
+	return &container{
902
+		client:       c,
903
+		hcsContainer: hc,
904
+		id:           id,
905
+	}, nil
906
+}
907
+
908
+// AttachTask is only called by the daemon when restoring containers. As
909
+// re-attach isn't possible (see LoadContainer), a NotFound error is
910
+// unconditionally returned to allow restore to make progress.
911
+func (*container) AttachTask(context.Context, libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
912
+	return nil, errdefs.NotFound(cerrdefs.ErrNotImplemented)
913
+}
914
+
915
+// Pids returns a list of process IDs running in a container. It is not
916
+// implemented on Windows.
917
+func (t *task) Pids(context.Context) ([]containerd.ProcessInfo, error) {
918
+	return nil, errors.New("not implemented on Windows")
919
+}
920
+
921
+// Summary returns a summary of the processes running in a container.
922
+// This is present in Windows to support docker top. In linux, the
923
+// engine shells out to ps to get process information. On Windows, as
924
+// the containers could be Hyper-V containers, they would not be
925
+// visible on the container host. However, libcontainerd does have
926
+// that information.
927
+func (t *task) Summary(_ context.Context) ([]libcontainerdtypes.Summary, error) {
928
+	hc, err := t.getHCSContainer()
929
+	if err != nil {
930
+		return nil, err
931
+	}
932
+
933
+	p, err := hc.ProcessList()
934
+	if err != nil {
935
+		return nil, err
936
+	}
937
+
938
+	pl := make([]libcontainerdtypes.Summary, len(p))
939
+	for i := range p {
940
+		pl[i] = libcontainerdtypes.Summary{
941
+			ImageName:                    p[i].ImageName,
942
+			CreatedAt:                    timestamppb.New(p[i].CreateTimestamp),
943
+			KernelTime_100Ns:             p[i].KernelTime100ns,
944
+			MemoryCommitBytes:            p[i].MemoryCommitBytes,
945
+			MemoryWorkingSetPrivateBytes: p[i].MemoryWorkingSetPrivateBytes,
946
+			MemoryWorkingSetSharedBytes:  p[i].MemoryWorkingSetSharedBytes,
947
+			ProcessID:                    p[i].ProcessId,
948
+			UserTime_100Ns:               p[i].UserTime100ns,
949
+			ExecID:                       "",
950
+		}
951
+	}
952
+	return pl, nil
953
+}
954
+
955
+func (p *process) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
956
+	select {
957
+	case <-ctx.Done():
958
+		return nil, errors.WithStack(ctx.Err())
959
+	case <-p.waitCh:
960
+	default:
961
+		return nil, errdefs.Conflict(errors.New("process is running"))
962
+	}
963
+	return p.exited, nil
964
+}
965
+
966
+func (t *task) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
967
+	select {
968
+	case <-ctx.Done():
969
+		return nil, errors.WithStack(ctx.Err())
970
+	case <-t.waitCh:
971
+	default:
972
+		return nil, errdefs.Conflict(errors.New("container is not stopped"))
973
+	}
974
+
975
+	t.ctr.mu.Lock()
976
+	defer t.ctr.mu.Unlock()
977
+	if err := t.assertIsCurrentTask(); err != nil {
978
+		return nil, err
979
+	}
980
+	t.ctr.task = nil
981
+	return t.exited, nil
982
+}
983
+
984
+func (t *task) ForceDelete(ctx context.Context) error {
985
+	select {
986
+	case <-t.waitCh: // Task is already stopped.
987
+		_, err := t.Delete(ctx)
988
+		return err
989
+	default:
990
+	}
991
+
992
+	if err := t.Kill(ctx, syscall.SIGKILL); err != nil {
993
+		return errors.Wrap(err, "could not force-kill task")
994
+	}
995
+
996
+	select {
997
+	case <-ctx.Done():
998
+		return ctx.Err()
999
+	case <-t.waitCh:
1000
+		_, err := t.Delete(ctx)
1001
+		return err
1002
+	}
1003
+}
1004
+
1005
+func (t *task) Status(ctx context.Context) (containerd.Status, error) {
1006
+	select {
1007
+	case <-t.waitCh:
1008
+		return containerd.Status{
1009
+			Status:     containerd.Stopped,
1010
+			ExitStatus: t.exited.ExitCode(),
1011
+			ExitTime:   t.exited.ExitTime(),
1012
+		}, nil
1013
+	default:
1014
+	}
1015
+
1016
+	t.ctr.mu.Lock()
1017
+	defer t.ctr.mu.Unlock()
1018
+	s := containerd.Running
1019
+	if t.ctr.isPaused {
1020
+		s = containerd.Paused
1021
+	}
1022
+	return containerd.Status{Status: s}, nil
1023
+}
1024
+
1025
+func (*task) UpdateResources(context.Context, *libcontainerdtypes.Resources) error {
1026
+	// Updating resource isn't supported on Windows
1027
+	// but we should return nil for enabling updating container
1028
+	return nil
1029
+}
1030
+
1031
+func (*task) CreateCheckpoint(context.Context, string, bool) error {
1032
+	return errors.New("Windows: Containers do not support checkpoints")
1033
+}
1034
+
1035
+// assertIsCurrentTask returns a non-nil error if the task has been deleted.
1036
+func (t *task) assertIsCurrentTask() error {
1037
+	if t.ctr.task != t {
1038
+		return errors.WithStack(errdefs.NotFound(fmt.Errorf("task %q not found", t.id)))
1039
+	}
1040
+	return nil
1041
+}
1042
+
1043
+// getHCSContainer returns a reference to the hcsshim Container for the task's
1044
+// container if neither the task nor container have been deleted.
1045
+//
1046
+// t.ctr.mu must not be locked by the calling goroutine when calling this
1047
+// function.
1048
+func (t *task) getHCSContainer() (hcsshim.Container, error) {
1049
+	t.ctr.mu.Lock()
1050
+	defer t.ctr.mu.Unlock()
1051
+	if err := t.assertIsCurrentTask(); err != nil {
1052
+		return nil, err
1053
+	}
1054
+	hc := t.ctr.hcsContainer
1055
+	if hc == nil {
1056
+		return nil, errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", t.ctr.id)))
1057
+	}
1058
+	return hc, nil
1059
+}
1060
+
1061
+// ctr mutex must be held when calling this function.
1062
+func (ctr *container) shutdownContainer() error {
1063
+	var err error
1064
+	const waitTimeout = time.Minute * 5
1065
+
1066
+	if !ctr.terminateInvoked {
1067
+		err = ctr.hcsContainer.Shutdown()
1068
+	}
1069
+
1070
+	if hcsshim.IsPending(err) || ctr.terminateInvoked {
1071
+		err = ctr.hcsContainer.WaitTimeout(waitTimeout)
1072
+	} else if hcsshim.IsAlreadyStopped(err) {
1073
+		err = nil
1074
+	}
1075
+
1076
+	if err != nil {
1077
+		ctr.client.logger.WithError(err).WithField("container", ctr.id).
1078
+			Debug("failed to shutdown container, terminating it")
1079
+		terminateErr := ctr.terminateContainer()
1080
+		if terminateErr != nil {
1081
+			ctr.client.logger.WithError(terminateErr).WithField("container", ctr.id).
1082
+				Error("failed to shutdown container, and subsequent terminate also failed")
1083
+			return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
1084
+		}
1085
+		return err
1086
+	}
1087
+
1088
+	return nil
1089
+}
1090
+
1091
+// ctr mutex must be held when calling this function.
1092
+func (ctr *container) terminateContainer() error {
1093
+	const terminateTimeout = time.Minute * 5
1094
+	ctr.terminateInvoked = true
1095
+	err := ctr.hcsContainer.Terminate()
1096
+
1097
+	if hcsshim.IsPending(err) {
1098
+		err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
1099
+	} else if hcsshim.IsAlreadyStopped(err) {
1100
+		err = nil
1101
+	}
1102
+
1103
+	if err != nil {
1104
+		ctr.client.logger.WithError(err).WithField("container", ctr.id).
1105
+			Debug("failed to terminate container")
1106
+		return err
1107
+	}
1108
+
1109
+	return nil
1110
+}
1111
+
1112
+func (p *process) reap() {
1113
+	logger := p.ctr.client.logger.WithFields(log.Fields{
1114
+		"container": p.ctr.id,
1115
+		"process":   p.id,
1116
+	})
1117
+
1118
+	var eventErr error
1119
+
1120
+	// Block indefinitely for the process to exit.
1121
+	if err := p.hcsProcess.Wait(); err != nil {
1122
+		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1123
+			logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
1124
+		}
1125
+		// Fall through here, do not return. This ensures we tell the
1126
+		// docker engine that the process/container has exited to avoid
1127
+		// a container being dropped on the floor.
1128
+	}
1129
+	exitedAt := time.Now()
1130
+
1131
+	exitCode, err := p.hcsProcess.ExitCode()
1132
+	if err != nil {
1133
+		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1134
+			logger.WithError(err).Warnf("unable to get exit code for process")
1135
+		}
1136
+		// Since we got an error retrieving the exit code, make sure that the
1137
+		// code we return doesn't incorrectly indicate success.
1138
+		exitCode = -1
1139
+
1140
+		// Fall through here, do not return. This ensures we tell the
1141
+		// docker engine that the process/container has exited to avoid
1142
+		// a container being dropped on the floor.
1143
+	}
1144
+
1145
+	p.mu.Lock()
1146
+	hcsProcess := p.hcsProcess
1147
+	p.hcsProcess = nil
1148
+	p.mu.Unlock()
1149
+
1150
+	if err := hcsProcess.Close(); err != nil {
1151
+		logger.WithError(err).Warnf("failed to cleanup hcs process resources")
1152
+		exitCode = -1
1153
+		eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
1154
+	}
1155
+
1156
+	// Explicit locking is not required as reads from exited are
1157
+	// synchronized using waitCh.
1158
+	p.exited = containerd.NewExitStatus(uint32(exitCode), exitedAt, nil)
1159
+	close(p.waitCh)
1160
+
1161
+	p.ctr.client.eventQ.Append(p.ctr.id, func() {
1162
+		ei := libcontainerdtypes.EventInfo{
1163
+			ContainerID: p.ctr.id,
1164
+			ProcessID:   p.id,
1165
+			Pid:         uint32(hcsProcess.Pid()),
1166
+			ExitCode:    uint32(exitCode),
1167
+			ExitedAt:    exitedAt,
1168
+			Error:       eventErr,
1169
+		}
1170
+		p.ctr.client.logger.WithFields(log.Fields{
1171
+			"container":  p.ctr.id,
1172
+			"event":      libcontainerdtypes.EventExit,
1173
+			"event-info": ei,
1174
+		}).Info("sending event")
1175
+		err := p.ctr.client.backend.ProcessEvent(p.ctr.id, libcontainerdtypes.EventExit, ei)
1176
+		if err != nil {
1177
+			p.ctr.client.logger.WithError(err).WithFields(log.Fields{
1178
+				"container":  p.ctr.id,
1179
+				"event":      libcontainerdtypes.EventExit,
1180
+				"event-info": ei,
1181
+			}).Error("failed to process event")
1182
+		}
1183
+	})
1184
+}
1185
+
1186
+func (ctr *container) Delete(context.Context) error {
1187
+	ctr.mu.Lock()
1188
+	defer ctr.mu.Unlock()
1189
+
1190
+	if ctr.hcsContainer == nil {
1191
+		return errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", ctr.id)))
1192
+	}
1193
+
1194
+	// Check that there is no task currently running.
1195
+	if ctr.task != nil {
1196
+		select {
1197
+		case <-ctr.task.waitCh:
1198
+		default:
1199
+			return errors.WithStack(errdefs.Conflict(errors.New("container is not stopped")))
1200
+		}
1201
+	}
1202
+
1203
+	var (
1204
+		logger = ctr.client.logger.WithFields(log.Fields{
1205
+			"container": ctr.id,
1206
+		})
1207
+		thisErr error
1208
+	)
1209
+
1210
+	if err := ctr.shutdownContainer(); err != nil {
1211
+		logger.WithError(err).Warn("failed to shutdown container")
1212
+		thisErr = errors.Wrap(err, "failed to shutdown container")
1213
+	} else {
1214
+		logger.Debug("completed container shutdown")
1215
+	}
1216
+
1217
+	if err := ctr.hcsContainer.Close(); err != nil {
1218
+		logger.WithError(err).Error("failed to clean hcs container resources")
1219
+		thisErr = errors.Wrap(err, "failed to terminate container")
1220
+	}
1221
+
1222
+	ctr.hcsContainer = nil
1223
+	return thisErr
1224
+}
0 1225
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package 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) (int, error) {
16
+	n, err := r.ReadCloser.Read(b)
17
+	if err != nil {
18
+		r.Once.Do(func() { r.ReadCloser.Close() })
19
+	}
20
+	return n, err
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
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package local
1
+
2
+import "strings"
3
+
4
+// setupEnvironmentVariables converts a string array of environment variables
5
+// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
6
+func setupEnvironmentVariables(a []string) map[string]string {
7
+	r := make(map[string]string)
8
+	for _, s := range a {
9
+		if k, v, ok := strings.Cut(s, "="); ok {
10
+			r[k] = v
11
+		}
12
+	}
13
+	return r
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package 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
+}
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"context"
5 5
 
6 6
 	containerd "github.com/containerd/containerd/v2/client"
7
-	"github.com/docker/docker/libcontainerd/local"
7
+	"github.com/docker/docker/daemon/internal/libcontainerd/local"
8 8
 	"github.com/docker/docker/libcontainerd/remote"
9 9
 	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
10 10
 	"github.com/docker/docker/pkg/system"
11 11
deleted file mode 100644
... ...
@@ -1,1225 +0,0 @@
1
-package local
2
-
3
-// This package contains the legacy in-proc calls in HCS using the v1 schema
4
-// for Windows runtime purposes.
5
-
6
-import (
7
-	"context"
8
-	"fmt"
9
-	"io"
10
-	"os"
11
-	"path/filepath"
12
-	"regexp"
13
-	"runtime"
14
-	"strings"
15
-	"sync"
16
-	"syscall"
17
-	"time"
18
-
19
-	"github.com/Microsoft/hcsshim"
20
-	containerd "github.com/containerd/containerd/v2/client"
21
-	"github.com/containerd/containerd/v2/pkg/cio"
22
-	cerrdefs "github.com/containerd/errdefs"
23
-	"github.com/containerd/log"
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/system"
28
-	"github.com/opencontainers/runtime-spec/specs-go"
29
-	"github.com/pkg/errors"
30
-	"golang.org/x/sys/windows"
31
-	"google.golang.org/protobuf/types/known/timestamppb"
32
-)
33
-
34
-type process struct {
35
-	// mu guards the mutable fields of this struct.
36
-	//
37
-	// Always lock mu before ctr's mutex to prevent deadlocks.
38
-	mu         sync.Mutex
39
-	id         string                 // Invariants: immutable
40
-	ctr        *container             // Invariants: immutable, ctr != nil
41
-	hcsProcess hcsshim.Process        // Is set to nil on process exit
42
-	exited     *containerd.ExitStatus // Valid iff waitCh is closed
43
-	waitCh     chan struct{}
44
-}
45
-
46
-type task struct {
47
-	process
48
-}
49
-
50
-type container struct {
51
-	mu sync.Mutex
52
-
53
-	// The ociSpec is required, as client.Create() needs a spec, but can
54
-	// be called from the RestartManager context which does not otherwise
55
-	// have access to the Spec
56
-	//
57
-	// A container value with ociSpec == nil represents a container which
58
-	// has been loaded with (*client).LoadContainer, and is ineligible to
59
-	// be Start()ed.
60
-	ociSpec *specs.Spec
61
-
62
-	hcsContainer hcsshim.Container // Is set to nil on container delete
63
-	isPaused     bool
64
-
65
-	client           *client
66
-	id               string
67
-	terminateInvoked bool
68
-
69
-	// task is a reference to the current task for the container. As a
70
-	// corollary, when task == nil the container has no current task: the
71
-	// container was never Start()ed or the task was Delete()d.
72
-	task *task
73
-}
74
-
75
-// defaultOwner is a tag passed to HCS to allow it to differentiate between
76
-// container creator management stacks. We hard code "docker" in the case
77
-// of docker.
78
-const defaultOwner = "docker"
79
-
80
-type client struct {
81
-	backend libcontainerdtypes.Backend
82
-	logger  *log.Entry
83
-	eventQ  queue.Queue
84
-}
85
-
86
-// NewClient creates a new local executor for windows
87
-func NewClient(ctx context.Context, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
88
-	return &client{
89
-		backend: b,
90
-		logger:  log.G(ctx).WithField("module", "libcontainerd"),
91
-	}, nil
92
-}
93
-
94
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
95
-	return containerd.Version{}, errors.New("not implemented on Windows")
96
-}
97
-
98
-// NewContainer is the entrypoint to create a container from a spec.
99
-// Table below shows the fields required for HCS JSON calling parameters,
100
-// where if not populated, is omitted.
101
-// +-----------------+--------------------------------------------+---------------------------------------------------+
102
-// |                 | Isolation=Process                          | Isolation=Hyper-V                                 |
103
-// +-----------------+--------------------------------------------+---------------------------------------------------+
104
-// | VolumePath      | \\?\\Volume{GUIDa}                         |                                                   |
105
-// | LayerFolderPath | %root%\windowsfilter\containerID           |                                                   |
106
-// | Layers[]        | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID        |
107
-// | HvRuntime       |                                            | ImagePath=%root%\BaseLayerID\UtilityVM            |
108
-// +-----------------+--------------------------------------------+---------------------------------------------------+
109
-//
110
-// Isolation=Process example:
111
-//
112
-//	{
113
-//		"SystemType": "Container",
114
-//		"Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
115
-//		"Owner": "docker",
116
-//		"VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
117
-//		"IgnoreFlushesDuringBoot": true,
118
-//		"LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
119
-//		"Layers": [{
120
-//			"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
121
-//			"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
122
-//		}],
123
-//		"HostName": "5e0055c814a6",
124
-//		"MappedDirectories": [],
125
-//		"HvPartition": false,
126
-//		"EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
127
-//	}
128
-//
129
-// Isolation=Hyper-V example:
130
-//
131
-//	{
132
-//		"SystemType": "Container",
133
-//		"Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
134
-//		"Owner": "docker",
135
-//		"IgnoreFlushesDuringBoot": true,
136
-//		"Layers": [{
137
-//			"ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
138
-//			"Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
139
-//		}],
140
-//		"HostName": "475c2c58933b",
141
-//		"MappedDirectories": [],
142
-//		"HvPartition": true,
143
-//		"EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
144
-//		"DNSSearchList": "a.com,b.com,c.com",
145
-//		"HvRuntime": {
146
-//			"ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
147
-//		},
148
-//	}
149
-func (c *client) NewContainer(_ context.Context, id string, spec *specs.Spec, _ string, _ interface{}, _ ...containerd.NewContainerOpts) (libcontainerdtypes.Container, error) {
150
-	if spec.Linux != nil {
151
-		return nil, errors.New("linux containers are not supported on this platform")
152
-	}
153
-	ctr, err := c.createWindows(id, spec)
154
-	if err != nil {
155
-		return nil, err
156
-	}
157
-
158
-	c.eventQ.Append(id, func() {
159
-		c.logger.WithFields(log.Fields{
160
-			"container": id,
161
-			"event":     libcontainerdtypes.EventCreate,
162
-		}).Info("sending event")
163
-
164
-		err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, libcontainerdtypes.EventInfo{
165
-			ContainerID: id,
166
-		})
167
-		if err != nil {
168
-			c.logger.WithFields(log.Fields{
169
-				"container": id,
170
-				"event":     libcontainerdtypes.EventCreate,
171
-				"error":     err,
172
-			}).Error("failed to process event")
173
-		}
174
-	})
175
-	return ctr, nil
176
-}
177
-
178
-func (c *client) createWindows(id string, spec *specs.Spec) (*container, error) {
179
-	logger := c.logger.WithField("container", id)
180
-	configuration := &hcsshim.ContainerConfig{
181
-		SystemType:              "Container",
182
-		Name:                    id,
183
-		Owner:                   defaultOwner,
184
-		IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
185
-		HostName:                spec.Hostname,
186
-		HvPartition:             false,
187
-	}
188
-
189
-	c.extractResourcesFromSpec(spec, configuration)
190
-
191
-	if spec.Windows.Resources != nil {
192
-		if spec.Windows.Resources.Storage != nil {
193
-			if spec.Windows.Resources.Storage.Bps != nil {
194
-				configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
195
-			}
196
-			if spec.Windows.Resources.Storage.Iops != nil {
197
-				configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
198
-			}
199
-		}
200
-	}
201
-
202
-	if spec.Windows.HyperV != nil {
203
-		configuration.HvPartition = true
204
-	}
205
-
206
-	if spec.Windows.Network != nil {
207
-		configuration.EndpointList = spec.Windows.Network.EndpointList
208
-		configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
209
-		if spec.Windows.Network.DNSSearchList != nil {
210
-			configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
211
-		}
212
-		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
213
-	}
214
-
215
-	if cs, ok := spec.Windows.CredentialSpec.(string); ok {
216
-		configuration.Credentials = cs
217
-	}
218
-
219
-	// We must have least two layers in the spec, the bottom one being a
220
-	// base image, the top one being the RW layer.
221
-	if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
222
-		return nil, fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
223
-	}
224
-
225
-	// Strip off the top-most layer as that's passed in separately to HCS
226
-	configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
227
-	layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
228
-
229
-	if configuration.HvPartition {
230
-		// We don't currently support setting the utility VM image explicitly.
231
-		// TODO circa RS5, this may be re-locatable.
232
-		if spec.Windows.HyperV.UtilityVMPath != "" {
233
-			return nil, errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
234
-		}
235
-
236
-		// Find the upper-most utility VM image.
237
-		var uvmImagePath string
238
-		for _, path := range layerFolders {
239
-			fullPath := filepath.Join(path, "UtilityVM")
240
-			_, err := os.Stat(fullPath)
241
-			if err == nil {
242
-				uvmImagePath = fullPath
243
-				break
244
-			}
245
-			if !os.IsNotExist(err) {
246
-				return nil, err
247
-			}
248
-		}
249
-		if uvmImagePath == "" {
250
-			return nil, errors.New("utility VM image could not be found")
251
-		}
252
-		configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
253
-
254
-		if spec.Root.Path != "" {
255
-			return nil, errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
256
-		}
257
-	} else {
258
-		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}\}\\$`
259
-		if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
260
-			return nil, fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
261
-		}
262
-		// HCS API requires the trailing backslash to be removed
263
-		configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
264
-	}
265
-
266
-	if spec.Root.Readonly {
267
-		return nil, errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
268
-	}
269
-
270
-	for _, layerPath := range layerFolders {
271
-		_, filename := filepath.Split(layerPath)
272
-		g, err := hcsshim.NameToGuid(filename)
273
-		if err != nil {
274
-			return nil, err
275
-		}
276
-		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
277
-			ID:   g.ToString(),
278
-			Path: layerPath,
279
-		})
280
-	}
281
-
282
-	// Add the mounts (volumes, bind mounts etc) to the structure
283
-	var mds []hcsshim.MappedDir
284
-	var mps []hcsshim.MappedPipe
285
-	for _, mount := range spec.Mounts {
286
-		const pipePrefix = `\\.\pipe\`
287
-		if mount.Type != "" {
288
-			return nil, fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
289
-		}
290
-		if strings.HasPrefix(mount.Destination, pipePrefix) {
291
-			mp := hcsshim.MappedPipe{
292
-				HostPath:          mount.Source,
293
-				ContainerPipeName: mount.Destination[len(pipePrefix):],
294
-			}
295
-			mps = append(mps, mp)
296
-		} else {
297
-			md := hcsshim.MappedDir{
298
-				HostPath:      mount.Source,
299
-				ContainerPath: mount.Destination,
300
-				ReadOnly:      false,
301
-			}
302
-			for _, o := range mount.Options {
303
-				if strings.ToLower(o) == "ro" {
304
-					md.ReadOnly = true
305
-				}
306
-			}
307
-			mds = append(mds, md)
308
-		}
309
-	}
310
-	configuration.MappedDirectories = mds
311
-	configuration.MappedPipes = mps
312
-
313
-	if len(spec.Windows.Devices) > 0 {
314
-		// Add any device assignments
315
-		if configuration.HvPartition {
316
-			return nil, errors.New("device assignment is not supported for HyperV containers")
317
-		}
318
-		for _, d := range spec.Windows.Devices {
319
-			// Per https://github.com/microsoft/hcsshim/blob/v0.9.2/internal/uvm/virtual_device.go#L17-L18,
320
-			// these represent an Interface Class GUID.
321
-			if d.IDType != "class" && d.IDType != "vpci-class-guid" {
322
-				return nil, errors.Errorf("device assignment of type '%s' is not supported", d.IDType)
323
-			}
324
-			configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
325
-		}
326
-	}
327
-
328
-	hcsContainer, err := hcsshim.CreateContainer(id, configuration)
329
-	if err != nil {
330
-		return nil, err
331
-	}
332
-
333
-	// Construct a container object for calling start on it.
334
-	ctr := &container{
335
-		client:       c,
336
-		id:           id,
337
-		ociSpec:      spec,
338
-		hcsContainer: hcsContainer,
339
-	}
340
-
341
-	logger.Debug("starting container")
342
-	if err := ctr.hcsContainer.Start(); err != nil {
343
-		logger.WithError(err).Error("failed to start container")
344
-		ctr.mu.Lock()
345
-		if err := ctr.terminateContainer(); err != nil {
346
-			logger.WithError(err).Error("failed to cleanup after a failed Start")
347
-		} else {
348
-			logger.Debug("cleaned up after failed Start by calling Terminate")
349
-		}
350
-		ctr.mu.Unlock()
351
-		return nil, err
352
-	}
353
-
354
-	logger.Debug("createWindows() completed successfully")
355
-	return ctr, nil
356
-}
357
-
358
-func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
359
-	if spec.Windows.Resources == nil {
360
-		return
361
-	}
362
-	if spec.Windows.Resources.CPU != nil {
363
-		if spec.Windows.Resources.CPU.Count != nil {
364
-			// This check is being done here rather than in adaptContainerSettings
365
-			// because we don't want to update the HostConfig in case this container
366
-			// is moved to a host with more CPUs than this one.
367
-			cpuCount := *spec.Windows.Resources.CPU.Count
368
-			hostCPUCount := uint64(runtime.NumCPU())
369
-			if cpuCount > hostCPUCount {
370
-				c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
371
-				cpuCount = hostCPUCount
372
-			}
373
-			configuration.ProcessorCount = uint32(cpuCount)
374
-		}
375
-		if spec.Windows.Resources.CPU.Shares != nil {
376
-			configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
377
-		}
378
-		if spec.Windows.Resources.CPU.Maximum != nil {
379
-			configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum)
380
-		}
381
-	}
382
-	if spec.Windows.Resources.Memory != nil {
383
-		if spec.Windows.Resources.Memory.Limit != nil {
384
-			configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
385
-		}
386
-	}
387
-}
388
-
389
-func (ctr *container) NewTask(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
390
-	ctr.mu.Lock()
391
-	defer ctr.mu.Unlock()
392
-
393
-	switch {
394
-	case ctr.ociSpec == nil:
395
-		return nil, errors.WithStack(errdefs.NotImplemented(errors.New("a restored container cannot be started")))
396
-	case ctr.task != nil:
397
-		return nil, errors.WithStack(errdefs.NotModified(cerrdefs.ErrAlreadyExists))
398
-	}
399
-
400
-	logger := ctr.client.logger.WithField("container", ctr.id)
401
-
402
-	// Note we always tell HCS to create stdout as it's required
403
-	// regardless of '-i' or '-t' options, so that docker can always grab
404
-	// the output through logs. We also tell HCS to always create stdin,
405
-	// even if it's not used - it will be closed shortly. Stderr is only
406
-	// created if it we're not -t.
407
-	var (
408
-		emulateConsole   bool
409
-		createStdErrPipe bool
410
-	)
411
-	if ctr.ociSpec.Process != nil {
412
-		emulateConsole = ctr.ociSpec.Process.Terminal
413
-		createStdErrPipe = !ctr.ociSpec.Process.Terminal
414
-	}
415
-
416
-	createProcessParms := &hcsshim.ProcessConfig{
417
-		EmulateConsole:   emulateConsole,
418
-		WorkingDirectory: ctr.ociSpec.Process.Cwd,
419
-		CreateStdInPipe:  true,
420
-		CreateStdOutPipe: true,
421
-		CreateStdErrPipe: createStdErrPipe,
422
-	}
423
-
424
-	if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
425
-		createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
426
-		createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
427
-	}
428
-
429
-	// Configure the environment for the process
430
-	createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
431
-
432
-	// Configure the CommandLine/CommandArgs
433
-	setCommandLineAndArgs(ctr.ociSpec.Process, createProcessParms)
434
-	logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
435
-
436
-	createProcessParms.User = ctr.ociSpec.Process.User.Username
437
-
438
-	// Start the command running in the container.
439
-	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
440
-	if err != nil {
441
-		logger.WithError(err).Error("CreateProcess() failed")
442
-		return nil, err
443
-	}
444
-
445
-	defer func() {
446
-		if retErr != nil {
447
-			if err := newProcess.Kill(); err != nil {
448
-				logger.WithError(err).Error("failed to kill process")
449
-			}
450
-			go func() {
451
-				if err := newProcess.Wait(); err != nil {
452
-					logger.WithError(err).Error("failed to wait for process")
453
-				}
454
-				if err := newProcess.Close(); err != nil {
455
-					logger.WithError(err).Error("failed to clean process resources")
456
-				}
457
-			}()
458
-		}
459
-	}()
460
-
461
-	pid := newProcess.Pid()
462
-	logger.WithField("pid", pid).Debug("init process started")
463
-
464
-	dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal)
465
-	if err != nil {
466
-		logger.WithError(err).Error("failed to get stdio pipes")
467
-		return nil, err
468
-	}
469
-	_, err = attachStdio(dio)
470
-	if err != nil {
471
-		logger.WithError(err).Error("failed to attach stdio")
472
-		return nil, err
473
-	}
474
-
475
-	t := &task{process{
476
-		id:         ctr.id,
477
-		ctr:        ctr,
478
-		hcsProcess: newProcess,
479
-		waitCh:     make(chan struct{}),
480
-	}}
481
-
482
-	// All fallible operations have succeeded so it is now safe to set the
483
-	// container's current task.
484
-	ctr.task = t
485
-
486
-	// Spin up a goroutine to notify the backend and clean up resources when
487
-	// the task exits. Defer until after the start event is sent so that the
488
-	// exit event is not sent out-of-order.
489
-	defer func() { go t.reap() }()
490
-
491
-	// Generate the associated event
492
-	ctr.client.eventQ.Append(ctr.id, func() {
493
-		ei := libcontainerdtypes.EventInfo{
494
-			ContainerID: ctr.id,
495
-			ProcessID:   t.id,
496
-			Pid:         uint32(pid),
497
-		}
498
-		ctr.client.logger.WithFields(log.Fields{
499
-			"container":  ctr.id,
500
-			"event":      libcontainerdtypes.EventStart,
501
-			"event-info": ei,
502
-		}).Info("sending event")
503
-		err := ctr.client.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei)
504
-		if err != nil {
505
-			ctr.client.logger.WithError(err).WithFields(log.Fields{
506
-				"container":  ei.ContainerID,
507
-				"event":      libcontainerdtypes.EventStart,
508
-				"event-info": ei,
509
-			}).Error("failed to process event")
510
-		}
511
-	})
512
-	logger.Debug("start() completed")
513
-	return t, nil
514
-}
515
-
516
-func (*task) Start(context.Context) error {
517
-	// No-op on Windows.
518
-	return nil
519
-}
520
-
521
-func (ctr *container) Task(context.Context) (libcontainerdtypes.Task, error) {
522
-	ctr.mu.Lock()
523
-	defer ctr.mu.Unlock()
524
-	if ctr.task == nil {
525
-		return nil, errdefs.NotFound(cerrdefs.ErrNotFound)
526
-	}
527
-	return ctr.task, nil
528
-}
529
-
530
-// setCommandLineAndArgs configures the HCS ProcessConfig based on an OCI process spec
531
-func setCommandLineAndArgs(process *specs.Process, createProcessParms *hcsshim.ProcessConfig) {
532
-	if process.CommandLine != "" {
533
-		createProcessParms.CommandLine = process.CommandLine
534
-	} else {
535
-		createProcessParms.CommandLine = system.EscapeArgs(process.Args)
536
-	}
537
-}
538
-
539
-func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
540
-	stdin, stdout, stderr, err := newProcess.Stdio()
541
-	if err != nil {
542
-		return nil, err
543
-	}
544
-
545
-	dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal)
546
-
547
-	// Convert io.ReadClosers to io.Readers
548
-	if stdout != nil {
549
-		dio.Stdout = io.NopCloser(&autoClosingReader{ReadCloser: stdout})
550
-	}
551
-	if stderr != nil {
552
-		dio.Stderr = io.NopCloser(&autoClosingReader{ReadCloser: stderr})
553
-	}
554
-	return dio, nil
555
-}
556
-
557
-// Exec launches a process in a running container.
558
-//
559
-// The processID argument is entirely informational. As there is no mechanism
560
-// (exposed through the libcontainerd interfaces) to enumerate or reference an
561
-// exec'd process by ID, uniqueness is not currently enforced.
562
-func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Process, retErr error) {
563
-	hcsContainer, err := t.getHCSContainer()
564
-	if err != nil {
565
-		return nil, err
566
-	}
567
-	logger := t.ctr.client.logger.WithFields(log.Fields{
568
-		"container": t.ctr.id,
569
-		"exec":      processID,
570
-	})
571
-
572
-	// Note we always tell HCS to
573
-	// create stdout as it's required regardless of '-i' or '-t' options, so that
574
-	// docker can always grab the output through logs. We also tell HCS to always
575
-	// create stdin, even if it's not used - it will be closed shortly. Stderr
576
-	// is only created if it we're not -t.
577
-	createProcessParms := &hcsshim.ProcessConfig{
578
-		CreateStdInPipe:  true,
579
-		CreateStdOutPipe: true,
580
-		CreateStdErrPipe: !spec.Terminal,
581
-	}
582
-	if spec.Terminal {
583
-		createProcessParms.EmulateConsole = true
584
-		if spec.ConsoleSize != nil {
585
-			createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height)
586
-			createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width)
587
-		}
588
-	}
589
-
590
-	// Take working directory from the process to add if it is defined,
591
-	// otherwise take from the first process.
592
-	if spec.Cwd != "" {
593
-		createProcessParms.WorkingDirectory = spec.Cwd
594
-	} else {
595
-		createProcessParms.WorkingDirectory = t.ctr.ociSpec.Process.Cwd
596
-	}
597
-
598
-	// Configure the environment for the process
599
-	createProcessParms.Environment = setupEnvironmentVariables(spec.Env)
600
-
601
-	// Configure the CommandLine/CommandArgs
602
-	setCommandLineAndArgs(spec, createProcessParms)
603
-	logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
604
-
605
-	createProcessParms.User = spec.User.Username
606
-
607
-	// Start the command running in the container.
608
-	newProcess, err := hcsContainer.CreateProcess(createProcessParms)
609
-	if err != nil {
610
-		logger.WithError(err).Errorf("exec's CreateProcess() failed")
611
-		return nil, err
612
-	}
613
-	defer func() {
614
-		if retErr != nil {
615
-			if err := newProcess.Kill(); err != nil {
616
-				logger.WithError(err).Error("failed to kill process")
617
-			}
618
-			go func() {
619
-				if err := newProcess.Wait(); err != nil {
620
-					logger.WithError(err).Error("failed to wait for process")
621
-				}
622
-				if err := newProcess.Close(); err != nil {
623
-					logger.WithError(err).Error("failed to clean process resources")
624
-				}
625
-			}()
626
-		}
627
-	}()
628
-
629
-	dio, err := newIOFromProcess(newProcess, spec.Terminal)
630
-	if err != nil {
631
-		logger.WithError(err).Error("failed to get stdio pipes")
632
-		return nil, err
633
-	}
634
-	// Tell the engine to attach streams back to the client
635
-	_, err = attachStdio(dio)
636
-	if err != nil {
637
-		return nil, err
638
-	}
639
-
640
-	p := &process{
641
-		id:         processID,
642
-		ctr:        t.ctr,
643
-		hcsProcess: newProcess,
644
-		waitCh:     make(chan struct{}),
645
-	}
646
-
647
-	// Spin up a goroutine to notify the backend and clean up resources when
648
-	// the process exits. Defer until after the start event is sent so that
649
-	// the exit event is not sent out-of-order.
650
-	defer func() { go p.reap() }()
651
-
652
-	pid := newProcess.Pid()
653
-	t.ctr.client.eventQ.Append(t.ctr.id, func() {
654
-		ei := libcontainerdtypes.EventInfo{
655
-			ContainerID: t.ctr.id,
656
-			ProcessID:   p.id,
657
-			Pid:         uint32(pid),
658
-		}
659
-		t.ctr.client.logger.WithFields(log.Fields{
660
-			"container":  t.ctr.id,
661
-			"event":      libcontainerdtypes.EventExecAdded,
662
-			"event-info": ei,
663
-		}).Info("sending event")
664
-		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecAdded, ei)
665
-		if err != nil {
666
-			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
667
-				"container":  t.ctr.id,
668
-				"event":      libcontainerdtypes.EventExecAdded,
669
-				"event-info": ei,
670
-			}).Error("failed to process event")
671
-		}
672
-		err = t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecStarted, ei)
673
-		if err != nil {
674
-			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
675
-				"container":  t.ctr.id,
676
-				"event":      libcontainerdtypes.EventExecStarted,
677
-				"event-info": ei,
678
-			}).Error("failed to process event")
679
-		}
680
-	})
681
-
682
-	return p, nil
683
-}
684
-
685
-func (p *process) Pid() uint32 {
686
-	p.mu.Lock()
687
-	hcsProcess := p.hcsProcess
688
-	p.mu.Unlock()
689
-	if hcsProcess == nil {
690
-		return 0
691
-	}
692
-	return uint32(hcsProcess.Pid())
693
-}
694
-
695
-func (p *process) Kill(_ context.Context, signal syscall.Signal) error {
696
-	p.mu.Lock()
697
-	hcsProcess := p.hcsProcess
698
-	p.mu.Unlock()
699
-	if hcsProcess == nil {
700
-		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
701
-	}
702
-	return hcsProcess.Kill()
703
-}
704
-
705
-// Kill handles `docker stop` on Windows. While Linux has support for
706
-// the full range of signals, signals aren't really implemented on Windows.
707
-// We fake supporting regular stop and -9 to force kill.
708
-func (t *task) Kill(_ context.Context, signal syscall.Signal) error {
709
-	hcsContainer, err := t.getHCSContainer()
710
-	if err != nil {
711
-		return err
712
-	}
713
-
714
-	logger := t.ctr.client.logger.WithFields(log.Fields{
715
-		"container": t.ctr.id,
716
-		"process":   t.id,
717
-		"pid":       t.Pid(),
718
-		"signal":    signal,
719
-	})
720
-	logger.Debug("Signal()")
721
-
722
-	var op string
723
-	if signal == syscall.SIGKILL {
724
-		// Terminate the compute system
725
-		t.ctr.mu.Lock()
726
-		t.ctr.terminateInvoked = true
727
-		t.ctr.mu.Unlock()
728
-		op, err = "terminate", hcsContainer.Terminate()
729
-	} else {
730
-		// Shut down the container
731
-		op, err = "shutdown", hcsContainer.Shutdown()
732
-	}
733
-	if err != nil {
734
-		if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
735
-			// ignore errors
736
-			logger.WithError(err).Errorf("failed to %s hccshim container", op)
737
-		}
738
-	}
739
-
740
-	return nil
741
-}
742
-
743
-// Resize handles a CLI event to resize an interactive docker run or docker
744
-// exec window.
745
-func (p *process) Resize(_ context.Context, width, height uint32) error {
746
-	p.mu.Lock()
747
-	hcsProcess := p.hcsProcess
748
-	p.mu.Unlock()
749
-	if hcsProcess == nil {
750
-		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
751
-	}
752
-
753
-	p.ctr.client.logger.WithFields(log.Fields{
754
-		"container": p.ctr.id,
755
-		"process":   p.id,
756
-		"height":    height,
757
-		"width":     width,
758
-		"pid":       hcsProcess.Pid(),
759
-	}).Debug("resizing")
760
-	return hcsProcess.ResizeConsole(uint16(width), uint16(height))
761
-}
762
-
763
-func (p *process) CloseStdin(context.Context) error {
764
-	p.mu.Lock()
765
-	hcsProcess := p.hcsProcess
766
-	p.mu.Unlock()
767
-	if hcsProcess == nil {
768
-		return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
769
-	}
770
-
771
-	return hcsProcess.CloseStdin()
772
-}
773
-
774
-// Pause handles pause requests for containers
775
-func (t *task) Pause(_ context.Context) error {
776
-	if t.ctr.ociSpec.Windows.HyperV == nil {
777
-		return errdefs.NotImplemented(errors.WithStack(errors.New("not implemented for containers using process isolation")))
778
-	}
779
-
780
-	t.ctr.mu.Lock()
781
-	defer t.ctr.mu.Unlock()
782
-
783
-	if err := t.assertIsCurrentTask(); err != nil {
784
-		return err
785
-	}
786
-	if t.ctr.hcsContainer == nil {
787
-		return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
788
-	}
789
-	if err := t.ctr.hcsContainer.Pause(); err != nil {
790
-		return err
791
-	}
792
-
793
-	t.ctr.isPaused = true
794
-
795
-	t.ctr.client.eventQ.Append(t.ctr.id, func() {
796
-		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
797
-			ContainerID: t.ctr.id,
798
-			ProcessID:   t.id,
799
-		})
800
-		t.ctr.client.logger.WithFields(log.Fields{
801
-			"container": t.ctr.id,
802
-			"event":     libcontainerdtypes.EventPaused,
803
-		}).Info("sending event")
804
-		if err != nil {
805
-			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
806
-				"container": t.ctr.id,
807
-				"event":     libcontainerdtypes.EventPaused,
808
-			}).Error("failed to process event")
809
-		}
810
-	})
811
-
812
-	return nil
813
-}
814
-
815
-// Resume handles resume requests for containers
816
-func (t *task) Resume(ctx context.Context) error {
817
-	if t.ctr.ociSpec.Windows.HyperV == nil {
818
-		return errors.New("cannot resume Windows Server Containers")
819
-	}
820
-
821
-	t.ctr.mu.Lock()
822
-	defer t.ctr.mu.Unlock()
823
-
824
-	if err := t.assertIsCurrentTask(); err != nil {
825
-		return err
826
-	}
827
-	if t.ctr.hcsContainer == nil {
828
-		return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
829
-	}
830
-	if err := t.ctr.hcsContainer.Resume(); err != nil {
831
-		return err
832
-	}
833
-
834
-	t.ctr.isPaused = false
835
-
836
-	t.ctr.client.eventQ.Append(t.ctr.id, func() {
837
-		err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
838
-			ContainerID: t.ctr.id,
839
-			ProcessID:   t.id,
840
-		})
841
-		t.ctr.client.logger.WithFields(log.Fields{
842
-			"container": t.ctr.id,
843
-			"event":     libcontainerdtypes.EventResumed,
844
-		}).Info("sending event")
845
-		if err != nil {
846
-			t.ctr.client.logger.WithError(err).WithFields(log.Fields{
847
-				"container": t.ctr.id,
848
-				"event":     libcontainerdtypes.EventResumed,
849
-			}).Error("failed to process event")
850
-		}
851
-	})
852
-
853
-	return nil
854
-}
855
-
856
-// Stats handles stats requests for containers
857
-func (t *task) Stats(_ context.Context) (*libcontainerdtypes.Stats, error) {
858
-	hc, err := t.getHCSContainer()
859
-	if err != nil {
860
-		return nil, err
861
-	}
862
-
863
-	readAt := time.Now()
864
-	s, err := hc.Statistics()
865
-	if err != nil {
866
-		return nil, err
867
-	}
868
-	return &libcontainerdtypes.Stats{
869
-		Read:     readAt,
870
-		HCSStats: &s,
871
-	}, nil
872
-}
873
-
874
-// LoadContainer is the handler for restoring a container
875
-func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtypes.Container, error) {
876
-	c.logger.WithField("container", id).Debug("LoadContainer()")
877
-
878
-	// TODO Windows: On RS1, a re-attach isn't possible.
879
-	// However, there is a scenario in which there is an issue.
880
-	// Consider a background container. The daemon dies unexpectedly.
881
-	// HCS will still have the compute service alive and running.
882
-	// For consistence, we call in to shoot it regardless if HCS knows about it
883
-	// We explicitly just log a warning if the terminate fails.
884
-	// Then we tell the backend the container exited.
885
-	hc, err := hcsshim.OpenContainer(id)
886
-	if err != nil {
887
-		return nil, errdefs.NotFound(errors.New("container not found"))
888
-	}
889
-	const terminateTimeout = time.Minute * 2
890
-	err = hc.Terminate()
891
-
892
-	if hcsshim.IsPending(err) {
893
-		err = hc.WaitTimeout(terminateTimeout)
894
-	} else if hcsshim.IsAlreadyStopped(err) {
895
-		err = nil
896
-	}
897
-
898
-	if err != nil {
899
-		c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
900
-		return nil, err
901
-	}
902
-	return &container{
903
-		client:       c,
904
-		hcsContainer: hc,
905
-		id:           id,
906
-	}, nil
907
-}
908
-
909
-// AttachTask is only called by the daemon when restoring containers. As
910
-// re-attach isn't possible (see LoadContainer), a NotFound error is
911
-// unconditionally returned to allow restore to make progress.
912
-func (*container) AttachTask(context.Context, libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
913
-	return nil, errdefs.NotFound(cerrdefs.ErrNotImplemented)
914
-}
915
-
916
-// Pids returns a list of process IDs running in a container. It is not
917
-// implemented on Windows.
918
-func (t *task) Pids(context.Context) ([]containerd.ProcessInfo, error) {
919
-	return nil, errors.New("not implemented on Windows")
920
-}
921
-
922
-// Summary returns a summary of the processes running in a container.
923
-// This is present in Windows to support docker top. In linux, the
924
-// engine shells out to ps to get process information. On Windows, as
925
-// the containers could be Hyper-V containers, they would not be
926
-// visible on the container host. However, libcontainerd does have
927
-// that information.
928
-func (t *task) Summary(_ context.Context) ([]libcontainerdtypes.Summary, error) {
929
-	hc, err := t.getHCSContainer()
930
-	if err != nil {
931
-		return nil, err
932
-	}
933
-
934
-	p, err := hc.ProcessList()
935
-	if err != nil {
936
-		return nil, err
937
-	}
938
-
939
-	pl := make([]libcontainerdtypes.Summary, len(p))
940
-	for i := range p {
941
-		pl[i] = libcontainerdtypes.Summary{
942
-			ImageName:                    p[i].ImageName,
943
-			CreatedAt:                    timestamppb.New(p[i].CreateTimestamp),
944
-			KernelTime_100Ns:             p[i].KernelTime100ns,
945
-			MemoryCommitBytes:            p[i].MemoryCommitBytes,
946
-			MemoryWorkingSetPrivateBytes: p[i].MemoryWorkingSetPrivateBytes,
947
-			MemoryWorkingSetSharedBytes:  p[i].MemoryWorkingSetSharedBytes,
948
-			ProcessID:                    p[i].ProcessId,
949
-			UserTime_100Ns:               p[i].UserTime100ns,
950
-			ExecID:                       "",
951
-		}
952
-	}
953
-	return pl, nil
954
-}
955
-
956
-func (p *process) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
957
-	select {
958
-	case <-ctx.Done():
959
-		return nil, errors.WithStack(ctx.Err())
960
-	case <-p.waitCh:
961
-	default:
962
-		return nil, errdefs.Conflict(errors.New("process is running"))
963
-	}
964
-	return p.exited, nil
965
-}
966
-
967
-func (t *task) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
968
-	select {
969
-	case <-ctx.Done():
970
-		return nil, errors.WithStack(ctx.Err())
971
-	case <-t.waitCh:
972
-	default:
973
-		return nil, errdefs.Conflict(errors.New("container is not stopped"))
974
-	}
975
-
976
-	t.ctr.mu.Lock()
977
-	defer t.ctr.mu.Unlock()
978
-	if err := t.assertIsCurrentTask(); err != nil {
979
-		return nil, err
980
-	}
981
-	t.ctr.task = nil
982
-	return t.exited, nil
983
-}
984
-
985
-func (t *task) ForceDelete(ctx context.Context) error {
986
-	select {
987
-	case <-t.waitCh: // Task is already stopped.
988
-		_, err := t.Delete(ctx)
989
-		return err
990
-	default:
991
-	}
992
-
993
-	if err := t.Kill(ctx, syscall.SIGKILL); err != nil {
994
-		return errors.Wrap(err, "could not force-kill task")
995
-	}
996
-
997
-	select {
998
-	case <-ctx.Done():
999
-		return ctx.Err()
1000
-	case <-t.waitCh:
1001
-		_, err := t.Delete(ctx)
1002
-		return err
1003
-	}
1004
-}
1005
-
1006
-func (t *task) Status(ctx context.Context) (containerd.Status, error) {
1007
-	select {
1008
-	case <-t.waitCh:
1009
-		return containerd.Status{
1010
-			Status:     containerd.Stopped,
1011
-			ExitStatus: t.exited.ExitCode(),
1012
-			ExitTime:   t.exited.ExitTime(),
1013
-		}, nil
1014
-	default:
1015
-	}
1016
-
1017
-	t.ctr.mu.Lock()
1018
-	defer t.ctr.mu.Unlock()
1019
-	s := containerd.Running
1020
-	if t.ctr.isPaused {
1021
-		s = containerd.Paused
1022
-	}
1023
-	return containerd.Status{Status: s}, nil
1024
-}
1025
-
1026
-func (*task) UpdateResources(context.Context, *libcontainerdtypes.Resources) error {
1027
-	// Updating resource isn't supported on Windows
1028
-	// but we should return nil for enabling updating container
1029
-	return nil
1030
-}
1031
-
1032
-func (*task) CreateCheckpoint(context.Context, string, bool) error {
1033
-	return errors.New("Windows: Containers do not support checkpoints")
1034
-}
1035
-
1036
-// assertIsCurrentTask returns a non-nil error if the task has been deleted.
1037
-func (t *task) assertIsCurrentTask() error {
1038
-	if t.ctr.task != t {
1039
-		return errors.WithStack(errdefs.NotFound(fmt.Errorf("task %q not found", t.id)))
1040
-	}
1041
-	return nil
1042
-}
1043
-
1044
-// getHCSContainer returns a reference to the hcsshim Container for the task's
1045
-// container if neither the task nor container have been deleted.
1046
-//
1047
-// t.ctr.mu must not be locked by the calling goroutine when calling this
1048
-// function.
1049
-func (t *task) getHCSContainer() (hcsshim.Container, error) {
1050
-	t.ctr.mu.Lock()
1051
-	defer t.ctr.mu.Unlock()
1052
-	if err := t.assertIsCurrentTask(); err != nil {
1053
-		return nil, err
1054
-	}
1055
-	hc := t.ctr.hcsContainer
1056
-	if hc == nil {
1057
-		return nil, errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", t.ctr.id)))
1058
-	}
1059
-	return hc, nil
1060
-}
1061
-
1062
-// ctr mutex must be held when calling this function.
1063
-func (ctr *container) shutdownContainer() error {
1064
-	var err error
1065
-	const waitTimeout = time.Minute * 5
1066
-
1067
-	if !ctr.terminateInvoked {
1068
-		err = ctr.hcsContainer.Shutdown()
1069
-	}
1070
-
1071
-	if hcsshim.IsPending(err) || ctr.terminateInvoked {
1072
-		err = ctr.hcsContainer.WaitTimeout(waitTimeout)
1073
-	} else if hcsshim.IsAlreadyStopped(err) {
1074
-		err = nil
1075
-	}
1076
-
1077
-	if err != nil {
1078
-		ctr.client.logger.WithError(err).WithField("container", ctr.id).
1079
-			Debug("failed to shutdown container, terminating it")
1080
-		terminateErr := ctr.terminateContainer()
1081
-		if terminateErr != nil {
1082
-			ctr.client.logger.WithError(terminateErr).WithField("container", ctr.id).
1083
-				Error("failed to shutdown container, and subsequent terminate also failed")
1084
-			return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
1085
-		}
1086
-		return err
1087
-	}
1088
-
1089
-	return nil
1090
-}
1091
-
1092
-// ctr mutex must be held when calling this function.
1093
-func (ctr *container) terminateContainer() error {
1094
-	const terminateTimeout = time.Minute * 5
1095
-	ctr.terminateInvoked = true
1096
-	err := ctr.hcsContainer.Terminate()
1097
-
1098
-	if hcsshim.IsPending(err) {
1099
-		err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
1100
-	} else if hcsshim.IsAlreadyStopped(err) {
1101
-		err = nil
1102
-	}
1103
-
1104
-	if err != nil {
1105
-		ctr.client.logger.WithError(err).WithField("container", ctr.id).
1106
-			Debug("failed to terminate container")
1107
-		return err
1108
-	}
1109
-
1110
-	return nil
1111
-}
1112
-
1113
-func (p *process) reap() {
1114
-	logger := p.ctr.client.logger.WithFields(log.Fields{
1115
-		"container": p.ctr.id,
1116
-		"process":   p.id,
1117
-	})
1118
-
1119
-	var eventErr error
1120
-
1121
-	// Block indefinitely for the process to exit.
1122
-	if err := p.hcsProcess.Wait(); err != nil {
1123
-		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1124
-			logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
1125
-		}
1126
-		// Fall through here, do not return. This ensures we tell the
1127
-		// docker engine that the process/container has exited to avoid
1128
-		// a container being dropped on the floor.
1129
-	}
1130
-	exitedAt := time.Now()
1131
-
1132
-	exitCode, err := p.hcsProcess.ExitCode()
1133
-	if err != nil {
1134
-		if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
1135
-			logger.WithError(err).Warnf("unable to get exit code for process")
1136
-		}
1137
-		// Since we got an error retrieving the exit code, make sure that the
1138
-		// code we return doesn't incorrectly indicate success.
1139
-		exitCode = -1
1140
-
1141
-		// Fall through here, do not return. This ensures we tell the
1142
-		// docker engine that the process/container has exited to avoid
1143
-		// a container being dropped on the floor.
1144
-	}
1145
-
1146
-	p.mu.Lock()
1147
-	hcsProcess := p.hcsProcess
1148
-	p.hcsProcess = nil
1149
-	p.mu.Unlock()
1150
-
1151
-	if err := hcsProcess.Close(); err != nil {
1152
-		logger.WithError(err).Warnf("failed to cleanup hcs process resources")
1153
-		exitCode = -1
1154
-		eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
1155
-	}
1156
-
1157
-	// Explicit locking is not required as reads from exited are
1158
-	// synchronized using waitCh.
1159
-	p.exited = containerd.NewExitStatus(uint32(exitCode), exitedAt, nil)
1160
-	close(p.waitCh)
1161
-
1162
-	p.ctr.client.eventQ.Append(p.ctr.id, func() {
1163
-		ei := libcontainerdtypes.EventInfo{
1164
-			ContainerID: p.ctr.id,
1165
-			ProcessID:   p.id,
1166
-			Pid:         uint32(hcsProcess.Pid()),
1167
-			ExitCode:    uint32(exitCode),
1168
-			ExitedAt:    exitedAt,
1169
-			Error:       eventErr,
1170
-		}
1171
-		p.ctr.client.logger.WithFields(log.Fields{
1172
-			"container":  p.ctr.id,
1173
-			"event":      libcontainerdtypes.EventExit,
1174
-			"event-info": ei,
1175
-		}).Info("sending event")
1176
-		err := p.ctr.client.backend.ProcessEvent(p.ctr.id, libcontainerdtypes.EventExit, ei)
1177
-		if err != nil {
1178
-			p.ctr.client.logger.WithError(err).WithFields(log.Fields{
1179
-				"container":  p.ctr.id,
1180
-				"event":      libcontainerdtypes.EventExit,
1181
-				"event-info": ei,
1182
-			}).Error("failed to process event")
1183
-		}
1184
-	})
1185
-}
1186
-
1187
-func (ctr *container) Delete(context.Context) error {
1188
-	ctr.mu.Lock()
1189
-	defer ctr.mu.Unlock()
1190
-
1191
-	if ctr.hcsContainer == nil {
1192
-		return errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", ctr.id)))
1193
-	}
1194
-
1195
-	// Check that there is no task currently running.
1196
-	if ctr.task != nil {
1197
-		select {
1198
-		case <-ctr.task.waitCh:
1199
-		default:
1200
-			return errors.WithStack(errdefs.Conflict(errors.New("container is not stopped")))
1201
-		}
1202
-	}
1203
-
1204
-	var (
1205
-		logger = ctr.client.logger.WithFields(log.Fields{
1206
-			"container": ctr.id,
1207
-		})
1208
-		thisErr error
1209
-	)
1210
-
1211
-	if err := ctr.shutdownContainer(); err != nil {
1212
-		logger.WithError(err).Warn("failed to shutdown container")
1213
-		thisErr = errors.Wrap(err, "failed to shutdown container")
1214
-	} else {
1215
-		logger.Debug("completed container shutdown")
1216
-	}
1217
-
1218
-	if err := ctr.hcsContainer.Close(); err != nil {
1219
-		logger.WithError(err).Error("failed to clean hcs container resources")
1220
-		thisErr = errors.Wrap(err, "failed to terminate container")
1221
-	}
1222
-
1223
-	ctr.hcsContainer = nil
1224
-	return thisErr
1225
-}
1226 1
deleted file mode 100644
... ...
@@ -1,40 +0,0 @@
1
-package local
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) (int, error) {
17
-	n, err := r.ReadCloser.Read(b)
18
-	if err != nil {
19
-		r.Once.Do(func() { r.ReadCloser.Close() })
20
-	}
21
-	return n, err
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 1
deleted file mode 100644
... ...
@@ -1,15 +0,0 @@
1
-package local
2
-
3
-import "strings"
4
-
5
-// setupEnvironmentVariables converts a string array of environment variables
6
-// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
7
-func setupEnvironmentVariables(a []string) map[string]string {
8
-	r := make(map[string]string)
9
-	for _, s := range a {
10
-		if k, v, ok := strings.Cut(s, "="); ok {
11
-			r[k] = v
12
-		}
13
-	}
14
-	return r
15
-}
16 1
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-package local
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
-}