1package hcsshim
2
3import (
4	"encoding/json"
5	"fmt"
6	"os"
7	"sync"
8	"syscall"
9	"time"
10
11	"github.com/sirupsen/logrus"
12)
13
14var (
15	defaultTimeout = time.Minute * 4
16)
17
18const (
19	pendingUpdatesQuery    = `{ "PropertyTypes" : ["PendingUpdates"]}`
20	statisticsQuery        = `{ "PropertyTypes" : ["Statistics"]}`
21	processListQuery       = `{ "PropertyTypes" : ["ProcessList"]}`
22	mappedVirtualDiskQuery = `{ "PropertyTypes" : ["MappedVirtualDisk"]}`
23)
24
25type container struct {
26	handleLock     sync.RWMutex
27	handle         hcsSystem
28	id             string
29	callbackNumber uintptr
30}
31
32// ContainerProperties holds the properties for a container and the processes running in that container
33type ContainerProperties struct {
34	ID                           string `json:"Id"`
35	Name                         string
36	SystemType                   string
37	Owner                        string
38	SiloGUID                     string                              `json:"SiloGuid,omitempty"`
39	RuntimeID                    string                              `json:"RuntimeId,omitempty"`
40	IsRuntimeTemplate            bool                                `json:",omitempty"`
41	RuntimeImagePath             string                              `json:",omitempty"`
42	Stopped                      bool                                `json:",omitempty"`
43	ExitType                     string                              `json:",omitempty"`
44	AreUpdatesPending            bool                                `json:",omitempty"`
45	ObRoot                       string                              `json:",omitempty"`
46	Statistics                   Statistics                          `json:",omitempty"`
47	ProcessList                  []ProcessListItem                   `json:",omitempty"`
48	MappedVirtualDiskControllers map[int]MappedVirtualDiskController `json:",omitempty"`
49}
50
51// MemoryStats holds the memory statistics for a container
52type MemoryStats struct {
53	UsageCommitBytes            uint64 `json:"MemoryUsageCommitBytes,omitempty"`
54	UsageCommitPeakBytes        uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
55	UsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
56}
57
58// ProcessorStats holds the processor statistics for a container
59type ProcessorStats struct {
60	TotalRuntime100ns  uint64 `json:",omitempty"`
61	RuntimeUser100ns   uint64 `json:",omitempty"`
62	RuntimeKernel100ns uint64 `json:",omitempty"`
63}
64
65// StorageStats holds the storage statistics for a container
66type StorageStats struct {
67	ReadCountNormalized  uint64 `json:",omitempty"`
68	ReadSizeBytes        uint64 `json:",omitempty"`
69	WriteCountNormalized uint64 `json:",omitempty"`
70	WriteSizeBytes       uint64 `json:",omitempty"`
71}
72
73// NetworkStats holds the network statistics for a container
74type NetworkStats struct {
75	BytesReceived          uint64 `json:",omitempty"`
76	BytesSent              uint64 `json:",omitempty"`
77	PacketsReceived        uint64 `json:",omitempty"`
78	PacketsSent            uint64 `json:",omitempty"`
79	DroppedPacketsIncoming uint64 `json:",omitempty"`
80	DroppedPacketsOutgoing uint64 `json:",omitempty"`
81	EndpointId             string `json:",omitempty"`
82	InstanceId             string `json:",omitempty"`
83}
84
85// Statistics is the structure returned by a statistics call on a container
86type Statistics struct {
87	Timestamp          time.Time      `json:",omitempty"`
88	ContainerStartTime time.Time      `json:",omitempty"`
89	Uptime100ns        uint64         `json:",omitempty"`
90	Memory             MemoryStats    `json:",omitempty"`
91	Processor          ProcessorStats `json:",omitempty"`
92	Storage            StorageStats   `json:",omitempty"`
93	Network            []NetworkStats `json:",omitempty"`
94}
95
96// ProcessList is the structure of an item returned by a ProcessList call on a container
97type ProcessListItem struct {
98	CreateTimestamp              time.Time `json:",omitempty"`
99	ImageName                    string    `json:",omitempty"`
100	KernelTime100ns              uint64    `json:",omitempty"`
101	MemoryCommitBytes            uint64    `json:",omitempty"`
102	MemoryWorkingSetPrivateBytes uint64    `json:",omitempty"`
103	MemoryWorkingSetSharedBytes  uint64    `json:",omitempty"`
104	ProcessId                    uint32    `json:",omitempty"`
105	UserTime100ns                uint64    `json:",omitempty"`
106}
107
108// MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container
109type MappedVirtualDiskController struct {
110	MappedVirtualDisks map[int]MappedVirtualDisk `json:",omitempty"`
111}
112
113// Type of Request Support in ModifySystem
114type RequestType string
115
116// Type of Resource Support in ModifySystem
117type ResourceType string
118
119// RequestType const
120const (
121	Add     RequestType  = "Add"
122	Remove  RequestType  = "Remove"
123	Network ResourceType = "Network"
124)
125
126// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
127// Supported resource types are Network and Request Types are Add/Remove
128type ResourceModificationRequestResponse struct {
129	Resource ResourceType `json:"ResourceType"`
130	Data     interface{}  `json:"Settings"`
131	Request  RequestType  `json:"RequestType,omitempty"`
132}
133
134// createContainerAdditionalJSON is read from the environment at initialisation
135// time. It allows an environment variable to define additional JSON which
136// is merged in the CreateContainer call to HCS.
137var createContainerAdditionalJSON string
138
139func init() {
140	createContainerAdditionalJSON = os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON")
141}
142
143// CreateContainer creates a new container with the given configuration but does not start it.
144func CreateContainer(id string, c *ContainerConfig) (Container, error) {
145	return createContainerWithJSON(id, c, "")
146}
147
148// CreateContainerWithJSON creates a new container with the given configuration but does not start it.
149// It is identical to CreateContainer except that optional additional JSON can be merged before passing to HCS.
150func CreateContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) {
151	return createContainerWithJSON(id, c, additionalJSON)
152}
153
154func createContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) {
155	operation := "CreateContainer"
156	title := "HCSShim::" + operation
157
158	container := &container{
159		id: id,
160	}
161
162	configurationb, err := json.Marshal(c)
163	if err != nil {
164		return nil, err
165	}
166
167	configuration := string(configurationb)
168	logrus.Debugf(title+" id=%s config=%s", id, configuration)
169
170	// Merge any additional JSON. Priority is given to what is passed in explicitly,
171	// falling back to what's set in the environment.
172	if additionalJSON == "" && createContainerAdditionalJSON != "" {
173		additionalJSON = createContainerAdditionalJSON
174	}
175	if additionalJSON != "" {
176		configurationMap := map[string]interface{}{}
177		if err := json.Unmarshal([]byte(configuration), &configurationMap); err != nil {
178			return nil, fmt.Errorf("failed to unmarshal %s: %s", configuration, err)
179		}
180
181		additionalMap := map[string]interface{}{}
182		if err := json.Unmarshal([]byte(additionalJSON), &additionalMap); err != nil {
183			return nil, fmt.Errorf("failed to unmarshal %s: %s", additionalJSON, err)
184		}
185
186		mergedMap := mergeMaps(additionalMap, configurationMap)
187		mergedJSON, err := json.Marshal(mergedMap)
188		if err != nil {
189			return nil, fmt.Errorf("failed to marshal merged configuration map %+v: %s", mergedMap, err)
190		}
191
192		configuration = string(mergedJSON)
193		logrus.Debugf(title+" id=%s merged config=%s", id, configuration)
194	}
195
196	var (
197		resultp  *uint16
198		identity syscall.Handle
199	)
200	createError := hcsCreateComputeSystem(id, configuration, identity, &container.handle, &resultp)
201
202	if createError == nil || IsPending(createError) {
203		if err := container.registerCallback(); err != nil {
204			// Terminate the container if it still exists. We're okay to ignore a failure here.
205			container.Terminate()
206			return nil, makeContainerError(container, operation, "", err)
207		}
208	}
209
210	err = processAsyncHcsResult(createError, resultp, container.callbackNumber, hcsNotificationSystemCreateCompleted, &defaultTimeout)
211	if err != nil {
212		if err == ErrTimeout {
213			// Terminate the container if it still exists. We're okay to ignore a failure here.
214			container.Terminate()
215		}
216		return nil, makeContainerError(container, operation, configuration, err)
217	}
218
219	logrus.Debugf(title+" succeeded id=%s handle=%d", id, container.handle)
220	return container, nil
221}
222
223// mergeMaps recursively merges map `fromMap` into map `ToMap`. Any pre-existing values
224// in ToMap are overwritten. Values in fromMap are added to ToMap.
225// From http://stackoverflow.com/questions/40491438/merging-two-json-strings-in-golang
226func mergeMaps(fromMap, ToMap interface{}) interface{} {
227	switch fromMap := fromMap.(type) {
228	case map[string]interface{}:
229		ToMap, ok := ToMap.(map[string]interface{})
230		if !ok {
231			return fromMap
232		}
233		for keyToMap, valueToMap := range ToMap {
234			if valueFromMap, ok := fromMap[keyToMap]; ok {
235				fromMap[keyToMap] = mergeMaps(valueFromMap, valueToMap)
236			} else {
237				fromMap[keyToMap] = valueToMap
238			}
239		}
240	case nil:
241		// merge(nil, map[string]interface{...}) -> map[string]interface{...}
242		ToMap, ok := ToMap.(map[string]interface{})
243		if ok {
244			return ToMap
245		}
246	}
247	return fromMap
248}
249
250// OpenContainer opens an existing container by ID.
251func OpenContainer(id string) (Container, error) {
252	operation := "OpenContainer"
253	title := "HCSShim::" + operation
254	logrus.Debugf(title+" id=%s", id)
255
256	container := &container{
257		id: id,
258	}
259
260	var (
261		handle  hcsSystem
262		resultp *uint16
263	)
264	err := hcsOpenComputeSystem(id, &handle, &resultp)
265	err = processHcsResult(err, resultp)
266	if err != nil {
267		return nil, makeContainerError(container, operation, "", err)
268	}
269
270	container.handle = handle
271
272	if err := container.registerCallback(); err != nil {
273		return nil, makeContainerError(container, operation, "", err)
274	}
275
276	logrus.Debugf(title+" succeeded id=%s handle=%d", id, handle)
277	return container, nil
278}
279
280// GetContainers gets a list of the containers on the system that match the query
281func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
282	operation := "GetContainers"
283	title := "HCSShim::" + operation
284
285	queryb, err := json.Marshal(q)
286	if err != nil {
287		return nil, err
288	}
289
290	query := string(queryb)
291	logrus.Debugf(title+" query=%s", query)
292
293	var (
294		resultp         *uint16
295		computeSystemsp *uint16
296	)
297	err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp)
298	err = processHcsResult(err, resultp)
299	if err != nil {
300		return nil, err
301	}
302
303	if computeSystemsp == nil {
304		return nil, ErrUnexpectedValue
305	}
306	computeSystemsRaw := convertAndFreeCoTaskMemBytes(computeSystemsp)
307	computeSystems := []ContainerProperties{}
308	if err := json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil {
309		return nil, err
310	}
311
312	logrus.Debugf(title + " succeeded")
313	return computeSystems, nil
314}
315
316// Start synchronously starts the container.
317func (container *container) Start() error {
318	container.handleLock.RLock()
319	defer container.handleLock.RUnlock()
320	operation := "Start"
321	title := "HCSShim::Container::" + operation
322	logrus.Debugf(title+" id=%s", container.id)
323
324	if container.handle == 0 {
325		return makeContainerError(container, operation, "", ErrAlreadyClosed)
326	}
327
328	var resultp *uint16
329	err := hcsStartComputeSystem(container.handle, "", &resultp)
330	err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemStartCompleted, &defaultTimeout)
331	if err != nil {
332		return makeContainerError(container, operation, "", err)
333	}
334
335	logrus.Debugf(title+" succeeded id=%s", container.id)
336	return nil
337}
338
339// Shutdown requests a container shutdown, if IsPending() on the error returned is true,
340// it may not actually be shut down until Wait() succeeds.
341func (container *container) Shutdown() error {
342	container.handleLock.RLock()
343	defer container.handleLock.RUnlock()
344	operation := "Shutdown"
345	title := "HCSShim::Container::" + operation
346	logrus.Debugf(title+" id=%s", container.id)
347
348	if container.handle == 0 {
349		return makeContainerError(container, operation, "", ErrAlreadyClosed)
350	}
351
352	var resultp *uint16
353	err := hcsShutdownComputeSystem(container.handle, "", &resultp)
354	err = processHcsResult(err, resultp)
355	if err != nil {
356		return makeContainerError(container, operation, "", err)
357	}
358
359	logrus.Debugf(title+" succeeded id=%s", container.id)
360	return nil
361}
362
363// Terminate requests a container terminate, if IsPending() on the error returned is true,
364// it may not actually be shut down until Wait() succeeds.
365func (container *container) Terminate() error {
366	container.handleLock.RLock()
367	defer container.handleLock.RUnlock()
368	operation := "Terminate"
369	title := "HCSShim::Container::" + operation
370	logrus.Debugf(title+" id=%s", container.id)
371
372	if container.handle == 0 {
373		return makeContainerError(container, operation, "", ErrAlreadyClosed)
374	}
375
376	var resultp *uint16
377	err := hcsTerminateComputeSystem(container.handle, "", &resultp)
378	err = processHcsResult(err, resultp)
379	if err != nil {
380		return makeContainerError(container, operation, "", err)
381	}
382
383	logrus.Debugf(title+" succeeded id=%s", container.id)
384	return nil
385}
386
387// Wait synchronously waits for the container to shutdown or terminate.
388func (container *container) Wait() error {
389	operation := "Wait"
390	title := "HCSShim::Container::" + operation
391	logrus.Debugf(title+" id=%s", container.id)
392
393	err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, nil)
394	if err != nil {
395		return makeContainerError(container, operation, "", err)
396	}
397
398	logrus.Debugf(title+" succeeded id=%s", container.id)
399	return nil
400}
401
402// WaitTimeout synchronously waits for the container to terminate or the duration to elapse.
403// If the timeout expires, IsTimeout(err) == true
404func (container *container) WaitTimeout(timeout time.Duration) error {
405	operation := "WaitTimeout"
406	title := "HCSShim::Container::" + operation
407	logrus.Debugf(title+" id=%s", container.id)
408
409	err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, &timeout)
410	if err != nil {
411		return makeContainerError(container, operation, "", err)
412	}
413
414	logrus.Debugf(title+" succeeded id=%s", container.id)
415	return nil
416}
417
418func (container *container) properties(query string) (*ContainerProperties, error) {
419	var (
420		resultp     *uint16
421		propertiesp *uint16
422	)
423	err := hcsGetComputeSystemProperties(container.handle, query, &propertiesp, &resultp)
424	err = processHcsResult(err, resultp)
425	if err != nil {
426		return nil, err
427	}
428
429	if propertiesp == nil {
430		return nil, ErrUnexpectedValue
431	}
432	propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp)
433	properties := &ContainerProperties{}
434	if err := json.Unmarshal(propertiesRaw, properties); err != nil {
435		return nil, err
436	}
437	return properties, nil
438}
439
440// HasPendingUpdates returns true if the container has updates pending to install
441func (container *container) HasPendingUpdates() (bool, error) {
442	container.handleLock.RLock()
443	defer container.handleLock.RUnlock()
444	operation := "HasPendingUpdates"
445	title := "HCSShim::Container::" + operation
446	logrus.Debugf(title+" id=%s", container.id)
447
448	if container.handle == 0 {
449		return false, makeContainerError(container, operation, "", ErrAlreadyClosed)
450	}
451
452	properties, err := container.properties(pendingUpdatesQuery)
453	if err != nil {
454		return false, makeContainerError(container, operation, "", err)
455	}
456
457	logrus.Debugf(title+" succeeded id=%s", container.id)
458	return properties.AreUpdatesPending, nil
459}
460
461// Statistics returns statistics for the container
462func (container *container) Statistics() (Statistics, error) {
463	container.handleLock.RLock()
464	defer container.handleLock.RUnlock()
465	operation := "Statistics"
466	title := "HCSShim::Container::" + operation
467	logrus.Debugf(title+" id=%s", container.id)
468
469	if container.handle == 0 {
470		return Statistics{}, makeContainerError(container, operation, "", ErrAlreadyClosed)
471	}
472
473	properties, err := container.properties(statisticsQuery)
474	if err != nil {
475		return Statistics{}, makeContainerError(container, operation, "", err)
476	}
477
478	logrus.Debugf(title+" succeeded id=%s", container.id)
479	return properties.Statistics, nil
480}
481
482// ProcessList returns an array of ProcessListItems for the container
483func (container *container) ProcessList() ([]ProcessListItem, error) {
484	container.handleLock.RLock()
485	defer container.handleLock.RUnlock()
486	operation := "ProcessList"
487	title := "HCSShim::Container::" + operation
488	logrus.Debugf(title+" id=%s", container.id)
489
490	if container.handle == 0 {
491		return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
492	}
493
494	properties, err := container.properties(processListQuery)
495	if err != nil {
496		return nil, makeContainerError(container, operation, "", err)
497	}
498
499	logrus.Debugf(title+" succeeded id=%s", container.id)
500	return properties.ProcessList, nil
501}
502
503// MappedVirtualDisks returns a map of the controllers and the disks mapped
504// to a container.
505//
506// Example of JSON returned by the query.
507//{
508//   "Id":"1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3_svm",
509//   "SystemType":"Container",
510//   "RuntimeOsType":"Linux",
511//   "RuntimeId":"00000000-0000-0000-0000-000000000000",
512//   "State":"Running",
513//   "MappedVirtualDiskControllers":{
514//      "0":{
515//         "MappedVirtualDisks":{
516//            "2":{
517//               "HostPath":"C:\\lcow\\lcow\\scratch\\1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3.vhdx",
518//               "ContainerPath":"/mnt/gcs/LinuxServiceVM/scratch",
519//               "Lun":2,
520//               "CreateInUtilityVM":true
521//            },
522//            "3":{
523//               "HostPath":"C:\\lcow\\lcow\\1126e8d7d279c707a666972a15976371d365eaf622c02cea2c442b84f6f550a3\\sandbox.vhdx",
524//               "Lun":3,
525//               "CreateInUtilityVM":true,
526//               "AttachOnly":true
527//            }
528//         }
529//      }
530//   }
531//}
532func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
533	container.handleLock.RLock()
534	defer container.handleLock.RUnlock()
535	operation := "MappedVirtualDiskList"
536	title := "HCSShim::Container::" + operation
537	logrus.Debugf(title+" id=%s", container.id)
538
539	if container.handle == 0 {
540		return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
541	}
542
543	properties, err := container.properties(mappedVirtualDiskQuery)
544	if err != nil {
545		return nil, makeContainerError(container, operation, "", err)
546	}
547
548	logrus.Debugf(title+" succeeded id=%s", container.id)
549	return properties.MappedVirtualDiskControllers, nil
550}
551
552// Pause pauses the execution of the container. This feature is not enabled in TP5.
553func (container *container) Pause() error {
554	container.handleLock.RLock()
555	defer container.handleLock.RUnlock()
556	operation := "Pause"
557	title := "HCSShim::Container::" + operation
558	logrus.Debugf(title+" id=%s", container.id)
559
560	if container.handle == 0 {
561		return makeContainerError(container, operation, "", ErrAlreadyClosed)
562	}
563
564	var resultp *uint16
565	err := hcsPauseComputeSystem(container.handle, "", &resultp)
566	err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemPauseCompleted, &defaultTimeout)
567	if err != nil {
568		return makeContainerError(container, operation, "", err)
569	}
570
571	logrus.Debugf(title+" succeeded id=%s", container.id)
572	return nil
573}
574
575// Resume resumes the execution of the container. This feature is not enabled in TP5.
576func (container *container) Resume() error {
577	container.handleLock.RLock()
578	defer container.handleLock.RUnlock()
579	operation := "Resume"
580	title := "HCSShim::Container::" + operation
581	logrus.Debugf(title+" id=%s", container.id)
582
583	if container.handle == 0 {
584		return makeContainerError(container, operation, "", ErrAlreadyClosed)
585	}
586
587	var resultp *uint16
588	err := hcsResumeComputeSystem(container.handle, "", &resultp)
589	err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemResumeCompleted, &defaultTimeout)
590	if err != nil {
591		return makeContainerError(container, operation, "", err)
592	}
593
594	logrus.Debugf(title+" succeeded id=%s", container.id)
595	return nil
596}
597
598// CreateProcess launches a new process within the container.
599func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
600	container.handleLock.RLock()
601	defer container.handleLock.RUnlock()
602	operation := "CreateProcess"
603	title := "HCSShim::Container::" + operation
604	var (
605		processInfo   hcsProcessInformation
606		processHandle hcsProcess
607		resultp       *uint16
608	)
609
610	if container.handle == 0 {
611		return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
612	}
613
614	// If we are not emulating a console, ignore any console size passed to us
615	if !c.EmulateConsole {
616		c.ConsoleSize[0] = 0
617		c.ConsoleSize[1] = 0
618	}
619
620	configurationb, err := json.Marshal(c)
621	if err != nil {
622		return nil, makeContainerError(container, operation, "", err)
623	}
624
625	configuration := string(configurationb)
626	logrus.Debugf(title+" id=%s config=%s", container.id, configuration)
627
628	err = hcsCreateProcess(container.handle, configuration, &processInfo, &processHandle, &resultp)
629	err = processHcsResult(err, resultp)
630	if err != nil {
631		return nil, makeContainerError(container, operation, configuration, err)
632	}
633
634	process := &process{
635		handle:    processHandle,
636		processID: int(processInfo.ProcessId),
637		container: container,
638		cachedPipes: &cachedPipes{
639			stdIn:  processInfo.StdInput,
640			stdOut: processInfo.StdOutput,
641			stdErr: processInfo.StdError,
642		},
643	}
644
645	if err := process.registerCallback(); err != nil {
646		return nil, makeContainerError(container, operation, "", err)
647	}
648
649	logrus.Debugf(title+" succeeded id=%s processid=%d", container.id, process.processID)
650	return process, nil
651}
652
653// OpenProcess gets an interface to an existing process within the container.
654func (container *container) OpenProcess(pid int) (Process, error) {
655	container.handleLock.RLock()
656	defer container.handleLock.RUnlock()
657	operation := "OpenProcess"
658	title := "HCSShim::Container::" + operation
659	logrus.Debugf(title+" id=%s, processid=%d", container.id, pid)
660	var (
661		processHandle hcsProcess
662		resultp       *uint16
663	)
664
665	if container.handle == 0 {
666		return nil, makeContainerError(container, operation, "", ErrAlreadyClosed)
667	}
668
669	err := hcsOpenProcess(container.handle, uint32(pid), &processHandle, &resultp)
670	err = processHcsResult(err, resultp)
671	if err != nil {
672		return nil, makeContainerError(container, operation, "", err)
673	}
674
675	process := &process{
676		handle:    processHandle,
677		processID: pid,
678		container: container,
679	}
680
681	if err := process.registerCallback(); err != nil {
682		return nil, makeContainerError(container, operation, "", err)
683	}
684
685	logrus.Debugf(title+" succeeded id=%s processid=%s", container.id, process.processID)
686	return process, nil
687}
688
689// Close cleans up any state associated with the container but does not terminate or wait for it.
690func (container *container) Close() error {
691	container.handleLock.Lock()
692	defer container.handleLock.Unlock()
693	operation := "Close"
694	title := "HCSShim::Container::" + operation
695	logrus.Debugf(title+" id=%s", container.id)
696
697	// Don't double free this
698	if container.handle == 0 {
699		return nil
700	}
701
702	if err := container.unregisterCallback(); err != nil {
703		return makeContainerError(container, operation, "", err)
704	}
705
706	if err := hcsCloseComputeSystem(container.handle); err != nil {
707		return makeContainerError(container, operation, "", err)
708	}
709
710	container.handle = 0
711
712	logrus.Debugf(title+" succeeded id=%s", container.id)
713	return nil
714}
715
716func (container *container) registerCallback() error {
717	context := &notifcationWatcherContext{
718		channels: newChannels(),
719	}
720
721	callbackMapLock.Lock()
722	callbackNumber := nextCallback
723	nextCallback++
724	callbackMap[callbackNumber] = context
725	callbackMapLock.Unlock()
726
727	var callbackHandle hcsCallback
728	err := hcsRegisterComputeSystemCallback(container.handle, notificationWatcherCallback, callbackNumber, &callbackHandle)
729	if err != nil {
730		return err
731	}
732	context.handle = callbackHandle
733	container.callbackNumber = callbackNumber
734
735	return nil
736}
737
738func (container *container) unregisterCallback() error {
739	callbackNumber := container.callbackNumber
740
741	callbackMapLock.RLock()
742	context := callbackMap[callbackNumber]
743	callbackMapLock.RUnlock()
744
745	if context == nil {
746		return nil
747	}
748
749	handle := context.handle
750
751	if handle == 0 {
752		return nil
753	}
754
755	// hcsUnregisterComputeSystemCallback has its own syncronization
756	// to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
757	err := hcsUnregisterComputeSystemCallback(handle)
758	if err != nil {
759		return err
760	}
761
762	closeChannels(context.channels)
763
764	callbackMapLock.Lock()
765	callbackMap[callbackNumber] = nil
766	callbackMapLock.Unlock()
767
768	handle = 0
769
770	return nil
771}
772
773// Modifies the System by sending a request to HCS
774func (container *container) Modify(config *ResourceModificationRequestResponse) error {
775	container.handleLock.RLock()
776	defer container.handleLock.RUnlock()
777	operation := "Modify"
778	title := "HCSShim::Container::" + operation
779
780	if container.handle == 0 {
781		return makeContainerError(container, operation, "", ErrAlreadyClosed)
782	}
783
784	requestJSON, err := json.Marshal(config)
785	if err != nil {
786		return err
787	}
788
789	requestString := string(requestJSON)
790	logrus.Debugf(title+" id=%s request=%s", container.id, requestString)
791
792	var resultp *uint16
793	err = hcsModifyComputeSystem(container.handle, requestString, &resultp)
794	err = processHcsResult(err, resultp)
795	if err != nil {
796		return makeContainerError(container, operation, "", err)
797	}
798	logrus.Debugf(title+" succeeded id=%s", container.id)
799	return nil
800}
801