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