github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/container_exec.go (about) 1 package libpod 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "strconv" 8 "time" 9 10 "github.com/containers/common/pkg/capabilities" 11 "github.com/containers/libpod/libpod/define" 12 "github.com/containers/libpod/libpod/events" 13 "github.com/containers/storage/pkg/stringid" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 "k8s.io/client-go/tools/remotecommand" 17 ) 18 19 // ExecConfig contains the configuration of an exec session 20 type ExecConfig struct { 21 // Command the the command that will be invoked in the exec session. 22 // Must not be empty. 23 Command []string `json:"command"` 24 // Terminal is whether the exec session will allocate a pseudoterminal. 25 Terminal bool `json:"terminal,omitempty"` 26 // AttachStdin is whether the STDIN stream will be forwarded to the exec 27 // session's first process when attaching. Only available if Terminal is 28 // false. 29 AttachStdin bool `json:"attachStdin,omitempty"` 30 // AttachStdout is whether the STDOUT stream will be forwarded to the 31 // exec session's first process when attaching. Only available if 32 // Terminal is false. 33 AttachStdout bool `json:"attachStdout,omitempty"` 34 // AttachStderr is whether the STDERR stream will be forwarded to the 35 // exec session's first process when attaching. Only available if 36 // Terminal is false. 37 AttachStderr bool `json:"attachStderr,omitempty"` 38 // DetachKeys are keys that will be used to detach from the exec 39 // session. Here, nil will use the default detach keys, where a pointer 40 // to the empty string ("") will disable detaching via detach keys. 41 DetachKeys *string `json:"detachKeys,omitempty"` 42 // Environment is a set of environment variables that will be set for 43 // the first process started by the exec session. 44 Environment map[string]string `json:"environment,omitempty"` 45 // Privileged is whether the exec session will be privileged - that is, 46 // will be granted additional capabilities. 47 Privileged bool `json:"privileged,omitempty"` 48 // User is the user the exec session will be run as. 49 // If set to "" the exec session will be started as the same user the 50 // container was started as. 51 User string `json:"user,omitempty"` 52 // WorkDir is the working directory for the first process that will be 53 // launched by the exec session. 54 // If set to "" the exec session will be started in / within the 55 // container. 56 WorkDir string `json:"workDir,omitempty"` 57 // PreserveFDs indicates that a number of extra FDs from the process 58 // running libpod will be passed into the container. These are assumed 59 // to begin at 3 (immediately after the standard streams). The number 60 // given is the number that will be passed into the exec session, 61 // starting at 3. 62 PreserveFDs uint `json:"preserveFds,omitempty"` 63 } 64 65 // ExecSession contains information on a single exec session attached to a given 66 // container. 67 type ExecSession struct { 68 // Id is the ID of the exec session. 69 // Named somewhat strangely to not conflict with ID(). 70 Id string `json:"id"` 71 // ContainerId is the ID of the container this exec session belongs to. 72 // Named somewhat strangely to not conflict with ContainerID(). 73 ContainerId string `json:"containerId"` 74 75 // State is the state of the exec session. 76 State define.ContainerExecStatus `json:"state"` 77 // PID is the PID of the process created by the exec session. 78 PID int `json:"pid,omitempty"` 79 // ExitCode is the exit code of the exec session, if it has exited. 80 ExitCode int `json:"exitCode,omitempty"` 81 82 // Config is the configuration of this exec session. 83 // Cannot be empty. 84 Config *ExecConfig `json:"config"` 85 } 86 87 // ID returns the ID of an exec session. 88 func (e *ExecSession) ID() string { 89 return e.Id 90 } 91 92 // ContainerID returns the ID of the container this exec session was started in. 93 func (e *ExecSession) ContainerID() string { 94 return e.ContainerId 95 } 96 97 // Inspect inspects the given exec session and produces detailed output on its 98 // configuration and current state. 99 func (e *ExecSession) Inspect() (*define.InspectExecSession, error) { 100 if e.Config == nil { 101 return nil, errors.Wrapf(define.ErrInternal, "given exec session does not have a configuration block") 102 } 103 104 output := new(define.InspectExecSession) 105 output.CanRemove = e.State != define.ExecStateRunning 106 output.ContainerID = e.ContainerId 107 if e.Config.DetachKeys != nil { 108 output.DetachKeys = *e.Config.DetachKeys 109 } 110 output.ExitCode = e.ExitCode 111 output.ID = e.Id 112 output.OpenStderr = e.Config.AttachStderr 113 output.OpenStdin = e.Config.AttachStdin 114 output.OpenStdout = e.Config.AttachStdout 115 output.Running = e.State == define.ExecStateRunning 116 output.Pid = e.PID 117 output.ProcessConfig = new(define.InspectExecProcess) 118 if len(e.Config.Command) > 0 { 119 output.ProcessConfig.Entrypoint = e.Config.Command[0] 120 if len(e.Config.Command) > 1 { 121 output.ProcessConfig.Arguments = make([]string, 0, len(e.Config.Command)-1) 122 output.ProcessConfig.Arguments = append(output.ProcessConfig.Arguments, e.Config.Command[1:]...) 123 } 124 } 125 output.ProcessConfig.Privileged = e.Config.Privileged 126 output.ProcessConfig.Tty = e.Config.Terminal 127 output.ProcessConfig.User = e.Config.User 128 129 return output, nil 130 } 131 132 // legacyExecSession contains information on an active exec session. It is a 133 // holdover from a previous Podman version and is DEPRECATED. 134 type legacyExecSession struct { 135 ID string `json:"id"` 136 Command []string `json:"command"` 137 PID int `json:"pid"` 138 } 139 140 // ExecCreate creates a new exec session for the container. 141 // The session is not started. The ID of the new exec session will be returned. 142 func (c *Container) ExecCreate(config *ExecConfig) (string, error) { 143 if !c.batched { 144 c.lock.Lock() 145 defer c.lock.Unlock() 146 147 if err := c.syncContainer(); err != nil { 148 return "", err 149 } 150 } 151 152 // Verify our config 153 if config == nil { 154 return "", errors.Wrapf(define.ErrInvalidArg, "must provide a configuration to ExecCreate") 155 } 156 if len(config.Command) == 0 { 157 return "", errors.Wrapf(define.ErrInvalidArg, "must provide a non-empty command to start an exec session") 158 } 159 if config.Terminal && (config.AttachStdin || config.AttachStdout || config.AttachStderr) { 160 return "", errors.Wrapf(define.ErrInvalidArg, "cannot specify streams to attach to when exec session has a pseudoterminal") 161 } 162 163 // Verify that we are in a good state to continue 164 if !c.ensureState(define.ContainerStateRunning) { 165 return "", errors.Wrapf(define.ErrCtrStateInvalid, "can only create exec sessions on running containers") 166 } 167 168 // Generate an ID for our new exec session 169 sessionID := stringid.GenerateNonCryptoID() 170 found := true 171 // This really ought to be a do-while, but Go doesn't have those... 172 for found { 173 found = false 174 for id := range c.state.ExecSessions { 175 if id == sessionID { 176 found = true 177 break 178 } 179 } 180 if found { 181 sessionID = stringid.GenerateNonCryptoID() 182 } 183 } 184 185 // Make our new exec session 186 session := new(ExecSession) 187 session.Id = sessionID 188 session.ContainerId = c.ID() 189 session.State = define.ExecStateCreated 190 session.Config = new(ExecConfig) 191 if err := JSONDeepCopy(config, session.Config); err != nil { 192 return "", errors.Wrapf(err, "error copying exec configuration into exec session") 193 } 194 195 if c.state.ExecSessions == nil { 196 c.state.ExecSessions = make(map[string]*ExecSession) 197 } 198 199 // Need to add to container state and exec session registry 200 c.state.ExecSessions[session.ID()] = session 201 if err := c.save(); err != nil { 202 return "", err 203 } 204 if err := c.runtime.state.AddExecSession(c, session); err != nil { 205 return "", err 206 } 207 208 logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID()) 209 210 return sessionID, nil 211 } 212 213 // ExecStart starts an exec session in the container, but does not attach to it. 214 // Returns immediately upon starting the exec session. 215 func (c *Container) ExecStart(sessionID string) error { 216 // Will be implemented in part 2, migrating Start and implementing 217 // detached Start. 218 return define.ErrNotImplemented 219 } 220 221 // ExecStartAndAttach starts and attaches to an exec session in a container. 222 // TODO: Should we include detach keys in the signature to allow override? 223 // TODO: How do we handle AttachStdin/AttachStdout/AttachStderr? 224 func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams) error { 225 if !c.batched { 226 c.lock.Lock() 227 defer c.lock.Unlock() 228 229 if err := c.syncContainer(); err != nil { 230 return err 231 } 232 } 233 234 // Verify that we are in a good state to continue 235 if !c.ensureState(define.ContainerStateRunning) { 236 return errors.Wrapf(define.ErrCtrStateInvalid, "can only start exec sessions when their container is running") 237 } 238 239 session, ok := c.state.ExecSessions[sessionID] 240 if !ok { 241 return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) 242 } 243 244 if session.State != define.ExecStateCreated { 245 return errors.Wrapf(define.ErrExecSessionStateInvalid, "can only start created exec sessions, while container %s session %s state is %q", c.ID(), session.ID(), session.State.String()) 246 } 247 248 logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) 249 250 // TODO: check logic here - should we set Privileged if the container is 251 // privileged? 252 var capList []string 253 if session.Config.Privileged || c.config.Privileged { 254 capList = capabilities.AllCapabilities() 255 } 256 257 user := c.config.User 258 if session.Config.User != "" { 259 user = session.Config.User 260 } 261 262 if err := c.createExecBundle(session.ID()); err != nil { 263 return err 264 } 265 266 opts := new(ExecOptions) 267 opts.Cmd = session.Config.Command 268 opts.CapAdd = capList 269 opts.Env = session.Config.Environment 270 opts.Terminal = session.Config.Terminal 271 opts.Cwd = session.Config.WorkDir 272 opts.User = user 273 opts.Streams = streams 274 opts.PreserveFDs = session.Config.PreserveFDs 275 opts.DetachKeys = session.Config.DetachKeys 276 277 pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts) 278 if err != nil { 279 return err 280 } 281 282 c.newContainerEvent(events.Exec) 283 logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) 284 285 var lastErr error 286 287 // Update and save session to reflect PID/running 288 session.PID = pid 289 session.State = define.ExecStateRunning 290 291 if err := c.save(); err != nil { 292 lastErr = err 293 } 294 295 // Unlock so other processes can use the container 296 if !c.batched { 297 c.lock.Unlock() 298 } 299 300 tmpErr := <-attachChan 301 if lastErr != nil { 302 logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) 303 } 304 lastErr = tmpErr 305 306 exitCode, err := c.readExecExitCode(session.ID()) 307 if err != nil { 308 if lastErr != nil { 309 logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) 310 } 311 lastErr = err 312 } 313 314 logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode) 315 316 // Lock again 317 if !c.batched { 318 c.lock.Lock() 319 } 320 321 // Sync the container to pick up state changes 322 if err := c.syncContainer(); err != nil { 323 if lastErr != nil { 324 logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) 325 } 326 return errors.Wrapf(err, "error syncing container %s state to remove exec session %s", c.ID(), session.ID()) 327 } 328 329 // Update status 330 // Since we did a syncContainer, the old session has been overwritten. 331 // Grab a fresh one from the database. 332 session, ok = c.state.ExecSessions[sessionID] 333 if !ok { 334 // Exec session already removed. 335 logrus.Infof("Container %s exec session %s already removed from database", c.ID(), sessionID) 336 return nil 337 } 338 session.State = define.ExecStateStopped 339 session.ExitCode = exitCode 340 session.PID = 0 341 342 if err := c.save(); err != nil { 343 if lastErr != nil { 344 logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) 345 } 346 lastErr = err 347 } 348 349 // Clean up after ourselves 350 if err := c.cleanupExecBundle(session.ID()); err != nil { 351 if lastErr != nil { 352 logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) 353 } 354 lastErr = err 355 } 356 357 return lastErr 358 } 359 360 // ExecHTTPStartAndAttach starts and performs an HTTP attach to an exec session. 361 func (c *Container) ExecHTTPStartAndAttach(sessionID string) error { 362 // Will be implemented in part 2, migrating Start. 363 return define.ErrNotImplemented 364 } 365 366 // ExecStop stops an exec session in the container. 367 // If a timeout is provided, it will be used; otherwise, the timeout will 368 // default to the stop timeout of the container. 369 // Cleanup will be invoked automatically once the session is stopped. 370 func (c *Container) ExecStop(sessionID string, timeout *uint) error { 371 if !c.batched { 372 c.lock.Lock() 373 defer c.lock.Unlock() 374 375 if err := c.syncContainer(); err != nil { 376 return err 377 } 378 } 379 380 session, ok := c.state.ExecSessions[sessionID] 381 if !ok { 382 return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) 383 } 384 385 if session.State != define.ExecStateRunning { 386 return errors.Wrapf(define.ErrExecSessionStateInvalid, "container %s exec session %s is %q, can only stop running sessions", c.ID(), session.ID(), session.State.String()) 387 } 388 389 logrus.Infof("Stopping container %s exec session %s", c.ID(), session.ID()) 390 391 finalTimeout := c.StopTimeout() 392 if timeout != nil { 393 finalTimeout = *timeout 394 } 395 396 // Stop the session 397 if err := c.ociRuntime.ExecStopContainer(c, session.ID(), finalTimeout); err != nil { 398 return err 399 } 400 401 var cleanupErr error 402 403 // Retrieve exit code and update status 404 exitCode, err := c.readExecExitCode(session.ID()) 405 if err != nil { 406 cleanupErr = err 407 } 408 session.ExitCode = exitCode 409 session.PID = 0 410 session.State = define.ExecStateStopped 411 412 if err := c.save(); err != nil { 413 if cleanupErr != nil { 414 logrus.Errorf("Error stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr) 415 } 416 cleanupErr = err 417 } 418 419 if err := c.cleanupExecBundle(session.ID()); err != nil { 420 if cleanupErr != nil { 421 logrus.Errorf("Error stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr) 422 } 423 cleanupErr = err 424 } 425 426 return cleanupErr 427 } 428 429 // ExecCleanup cleans up an exec session in the container, removing temporary 430 // files associated with it. 431 func (c *Container) ExecCleanup(sessionID string) error { 432 if !c.batched { 433 c.lock.Lock() 434 defer c.lock.Unlock() 435 436 if err := c.syncContainer(); err != nil { 437 return err 438 } 439 } 440 441 session, ok := c.state.ExecSessions[sessionID] 442 if !ok { 443 return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) 444 } 445 446 if session.State == define.ExecStateRunning { 447 return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot clean up container %s exec session %s as it is running", c.ID(), session.ID()) 448 } 449 450 logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID()) 451 452 return c.cleanupExecBundle(session.ID()) 453 } 454 455 // ExecRemove removes an exec session in the container. 456 // If force is given, the session will be stopped first if it is running. 457 func (c *Container) ExecRemove(sessionID string, force bool) error { 458 if !c.batched { 459 c.lock.Lock() 460 defer c.lock.Unlock() 461 462 if err := c.syncContainer(); err != nil { 463 return err 464 } 465 } 466 467 session, ok := c.state.ExecSessions[sessionID] 468 if !ok { 469 return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) 470 } 471 472 logrus.Infof("Removing container %s exec session %s", c.ID(), session.ID()) 473 474 // Update status of exec session if running, so we cna check if it 475 // stopped in the meantime. 476 if session.State == define.ExecStateRunning { 477 stopped, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) 478 if err != nil { 479 return err 480 } 481 if stopped { 482 session.State = define.ExecStateStopped 483 // TODO: should we retrieve exit code here? 484 // TODO: Might be worth saving state here. 485 } 486 } 487 488 if session.State == define.ExecStateRunning { 489 if !force { 490 return errors.Wrapf(define.ErrExecSessionStateInvalid, "container %s exec session %s is still running, cannot remove", c.ID(), session.ID()) 491 } 492 493 // Stop the session 494 if err := c.ociRuntime.ExecStopContainer(c, session.ID(), c.StopTimeout()); err != nil { 495 return err 496 } 497 498 if err := c.cleanupExecBundle(session.ID()); err != nil { 499 return err 500 } 501 } 502 503 // First remove exec session from DB. 504 if err := c.runtime.state.RemoveExecSession(session); err != nil { 505 return err 506 } 507 // Next, remove it from the container and save state 508 delete(c.state.ExecSessions, sessionID) 509 if err := c.save(); err != nil { 510 return err 511 } 512 513 logrus.Debugf("Successfully removed container %s exec session %s", c.ID(), session.ID()) 514 515 return nil 516 } 517 518 // ExecResize resizes the TTY of the given exec session. Only available if the 519 // exec session created a TTY. 520 func (c *Container) ExecResize(sessionID string, newSize remotecommand.TerminalSize) error { 521 if !c.batched { 522 c.lock.Lock() 523 defer c.lock.Unlock() 524 525 if err := c.syncContainer(); err != nil { 526 return err 527 } 528 } 529 530 session, ok := c.state.ExecSessions[sessionID] 531 if !ok { 532 return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) 533 } 534 535 logrus.Infof("Resizing container %s exec session %s to %+v", c.ID(), session.ID(), newSize) 536 537 if session.State != define.ExecStateRunning { 538 return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot resize container %s exec session %s as it is not running", c.ID(), session.ID()) 539 } 540 541 return c.ociRuntime.ExecAttachResize(c, sessionID, newSize) 542 } 543 544 // Exec emulates the old Libpod exec API, providing a single call to create, 545 // run, and remove an exec session. Returns exit code and error. Exit code is 546 // not guaranteed to be set sanely if error is not nil. 547 func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) { 548 sessionID, err := c.ExecCreate(config) 549 if err != nil { 550 return -1, err 551 } 552 553 // Start resizing if we have a resize channel. 554 // This goroutine may likely leak, given that we cannot close it here. 555 // Not a big deal, since it should run for as long as the Podman process 556 // does. Could be a big deal for `podman service` but we don't need this 557 // API there. 558 // TODO: Refactor so this is closed here, before we remove the exec 559 // session. 560 if resize != nil { 561 go func() { 562 logrus.Debugf("Sending resize events to exec session %s", sessionID) 563 for resizeRequest := range resize { 564 if err := c.ExecResize(sessionID, resizeRequest); err != nil { 565 // Assume the exec session went down. 566 logrus.Warnf("Error resizing exec session %s: %v", sessionID, err) 567 return 568 } 569 } 570 }() 571 } 572 573 if err := c.ExecStartAndAttach(sessionID, streams); err != nil { 574 return -1, err 575 } 576 577 session, err := c.ExecSession(sessionID) 578 if err != nil { 579 return -1, err 580 } 581 exitCode := session.ExitCode 582 if err := c.ExecRemove(sessionID, false); err != nil { 583 return -1, err 584 } 585 586 if exitCode != 0 { 587 return exitCode, errors.Wrapf(define.ErrOCIRuntime, "exec session exited with non-zero exit code %d", exitCode) 588 } 589 590 return exitCode, nil 591 } 592 593 // cleanup an exec session after its done 594 func (c *Container) cleanupExecBundle(sessionID string) error { 595 if err := os.RemoveAll(c.execBundlePath(sessionID)); err != nil && !os.IsNotExist(err) { 596 return err 597 } 598 599 return c.ociRuntime.ExecContainerCleanup(c, sessionID) 600 } 601 602 // the path to a containers exec session bundle 603 func (c *Container) execBundlePath(sessionID string) string { 604 return filepath.Join(c.bundlePath(), sessionID) 605 } 606 607 // Get PID file path for a container's exec session 608 func (c *Container) execPidPath(sessionID string) string { 609 return filepath.Join(c.execBundlePath(sessionID), "exec_pid") 610 } 611 612 // the log path for an exec session 613 func (c *Container) execLogPath(sessionID string) string { 614 return filepath.Join(c.execBundlePath(sessionID), "exec_log") 615 } 616 617 // the socket conmon creates for an exec session 618 func (c *Container) execAttachSocketPath(sessionID string) (string, error) { 619 return c.ociRuntime.ExecAttachSocketPath(c, sessionID) 620 } 621 622 // execExitFileDir gets the path to the container's exit file 623 func (c *Container) execExitFileDir(sessionID string) string { 624 return filepath.Join(c.execBundlePath(sessionID), "exit") 625 } 626 627 // execOCILog returns the file path for the exec sessions oci log 628 func (c *Container) execOCILog(sessionID string) string { 629 if !c.ociRuntime.SupportsJSONErrors() { 630 return "" 631 } 632 return filepath.Join(c.execBundlePath(sessionID), "oci-log") 633 } 634 635 // create a bundle path and associated files for an exec session 636 func (c *Container) createExecBundle(sessionID string) (err error) { 637 bundlePath := c.execBundlePath(sessionID) 638 if createErr := os.MkdirAll(bundlePath, execDirPermission); createErr != nil { 639 return createErr 640 } 641 defer func() { 642 if err != nil { 643 if err2 := os.RemoveAll(bundlePath); err != nil { 644 logrus.Warnf("error removing exec bundle after creation caused another error: %v", err2) 645 } 646 } 647 }() 648 if err2 := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err2 != nil { 649 // The directory is allowed to exist 650 if !os.IsExist(err2) { 651 err = errors.Wrapf(err2, "error creating OCI runtime exit file path %s", c.execExitFileDir(sessionID)) 652 } 653 } 654 return 655 } 656 657 // readExecExitCode reads the exit file for an exec session and returns 658 // the exit code 659 func (c *Container) readExecExitCode(sessionID string) (int, error) { 660 exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID()) 661 chWait := make(chan error) 662 defer close(chWait) 663 664 _, err := WaitForFile(exitFile, chWait, time.Second*5) 665 if err != nil { 666 return -1, err 667 } 668 ec, err := ioutil.ReadFile(exitFile) 669 if err != nil { 670 return -1, err 671 } 672 ecInt, err := strconv.Atoi(string(ec)) 673 if err != nil { 674 return -1, err 675 } 676 return ecInt, nil 677 } 678 679 // getExecSessionPID gets the PID of an active exec session 680 func (c *Container) getExecSessionPID(sessionID string) (int, error) { 681 session, ok := c.state.ExecSessions[sessionID] 682 if ok { 683 return session.PID, nil 684 } 685 oldSession, ok := c.state.LegacyExecSessions[sessionID] 686 if ok { 687 return oldSession.PID, nil 688 } 689 690 return -1, errors.Wrapf(define.ErrNoSuchExecSession, "no exec session with ID %s found in container %s", sessionID, c.ID()) 691 } 692 693 // getKnownExecSessions gets a list of all exec sessions we think are running, 694 // but does not verify their current state. 695 // Please use getActiveExecSessions() outside of container_exec.go, as this 696 // function performs further checks to return an accurate list. 697 func (c *Container) getKnownExecSessions() []string { 698 knownSessions := []string{} 699 // First check legacy sessions. 700 // TODO: This is DEPRECATED and will be removed in a future major 701 // release. 702 for sessionID := range c.state.LegacyExecSessions { 703 knownSessions = append(knownSessions, sessionID) 704 } 705 // Next check new exec sessions, but only if in running state 706 for sessionID, session := range c.state.ExecSessions { 707 if session.State == define.ExecStateRunning { 708 knownSessions = append(knownSessions, sessionID) 709 } 710 } 711 712 return knownSessions 713 } 714 715 // getActiveExecSessions checks if there are any active exec sessions in the 716 // current container. Returns an array of active exec sessions. 717 // Will continue through errors where possible. 718 // Currently handles both new and legacy, deprecated exec sessions. 719 func (c *Container) getActiveExecSessions() ([]string, error) { 720 activeSessions := []string{} 721 knownSessions := c.getKnownExecSessions() 722 723 // Instead of saving once per iteration, do it once at the end. 724 var lastErr error 725 needSave := false 726 for _, id := range knownSessions { 727 alive, err := c.ociRuntime.ExecUpdateStatus(c, id) 728 if err != nil { 729 if lastErr != nil { 730 logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr) 731 } 732 lastErr = err 733 continue 734 } 735 if !alive { 736 if err := c.cleanupExecBundle(id); err != nil { 737 if lastErr != nil { 738 logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr) 739 } 740 lastErr = err 741 } 742 743 _, isLegacy := c.state.LegacyExecSessions[id] 744 if isLegacy { 745 delete(c.state.LegacyExecSessions, id) 746 needSave = true 747 } else { 748 session := c.state.ExecSessions[id] 749 exitCode, err := c.readExecExitCode(session.ID()) 750 if err != nil { 751 if lastErr != nil { 752 logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr) 753 } 754 lastErr = err 755 } 756 session.ExitCode = exitCode 757 session.PID = 0 758 session.State = define.ExecStateStopped 759 760 needSave = true 761 } 762 } else { 763 activeSessions = append(activeSessions, id) 764 } 765 } 766 if needSave { 767 if err := c.save(); err != nil { 768 if lastErr != nil { 769 logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr) 770 } 771 lastErr = err 772 } 773 } 774 775 return activeSessions, lastErr 776 } 777 778 // removeAllExecSessions stops and removes all the container's exec sessions 779 func (c *Container) removeAllExecSessions() error { 780 knownSessions := c.getKnownExecSessions() 781 782 var lastErr error 783 for _, id := range knownSessions { 784 if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil { 785 if lastErr != nil { 786 logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr) 787 } 788 lastErr = err 789 continue 790 } 791 792 if err := c.cleanupExecBundle(id); err != nil { 793 if lastErr != nil { 794 logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr) 795 } 796 lastErr = err 797 } 798 } 799 // Delete all exec sessions 800 if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil { 801 if lastErr != nil { 802 logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr) 803 } 804 lastErr = err 805 } 806 c.state.ExecSessions = nil 807 c.state.LegacyExecSessions = nil 808 if err := c.save(); err != nil { 809 if lastErr != nil { 810 logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr) 811 } 812 lastErr = err 813 } 814 815 return lastErr 816 }