15
"github.com/containers/common/pkg/resize"
16
"github.com/containers/common/pkg/util"
17
"github.com/containers/podman/v5/libpod/define"
18
"github.com/containers/podman/v5/libpod/events"
19
"github.com/containers/storage/pkg/stringid"
20
"github.com/sirupsen/logrus"
21
"golang.org/x/sys/unix"
24
// ExecConfig contains the configuration of an exec session
25
type ExecConfig struct {
26
// Command is the command that will be invoked in the exec session.
28
Command []string `json:"command"`
29
// Terminal is whether the exec session will allocate a pseudoterminal.
30
Terminal bool `json:"terminal,omitempty"`
31
// AttachStdin is whether the STDIN stream will be forwarded to the exec
32
// session's first process when attaching. Only available if Terminal is
34
AttachStdin bool `json:"attachStdin,omitempty"`
35
// AttachStdout is whether the STDOUT stream will be forwarded to the
36
// exec session's first process when attaching. Only available if
38
AttachStdout bool `json:"attachStdout,omitempty"`
39
// AttachStderr is whether the STDERR stream will be forwarded to the
40
// exec session's first process when attaching. Only available if
42
AttachStderr bool `json:"attachStderr,omitempty"`
43
// DetachKeys are keys that will be used to detach from the exec
44
// session. Here, nil will use the default detach keys, where a pointer
45
// to the empty string ("") will disable detaching via detach keys.
46
DetachKeys *string `json:"detachKeys,omitempty"`
47
// Environment is a set of environment variables that will be set for
48
// the first process started by the exec session.
49
Environment map[string]string `json:"environment,omitempty"`
50
// Privileged is whether the exec session will be privileged - that is,
51
// will be granted additional capabilities.
52
Privileged bool `json:"privileged,omitempty"`
53
// User is the user the exec session will be run as.
54
// If set to "" the exec session will be started as the same user the
55
// container was started as.
56
User string `json:"user,omitempty"`
57
// WorkDir is the working directory for the first process that will be
58
// launched by the exec session.
59
// If set to "" the exec session will be started in / within the
61
WorkDir string `json:"workDir,omitempty"`
62
// PreserveFDs indicates that a number of extra FDs from the process
63
// running libpod will be passed into the container. These are assumed
64
// to begin at 3 (immediately after the standard streams). The number
65
// given is the number that will be passed into the exec session,
67
PreserveFDs uint `json:"preserveFds,omitempty"`
68
// PreserveFD is a list of additional file descriptors (in addition
69
// to 0, 1, 2) that will be passed to the executed process.
70
PreserveFD []uint `json:"preserveFd,omitempty"`
71
// ExitCommand is the exec session's exit command.
72
// This command will be executed when the exec session exits.
73
// If unset, no command will be executed.
74
// Two arguments will be appended to the exit command by Libpod:
75
// The ID of the exec session, and the ID of the container the exec
76
// session is a part of (in that order).
77
ExitCommand []string `json:"exitCommand,omitempty"`
78
// ExitCommandDelay is a delay (in seconds) between the container
79
// exiting, and the exit command being executed. If set to 0, there is
80
// no delay. If set, ExitCommand must also be set.
81
ExitCommandDelay uint `json:"exitCommandDelay,omitempty"`
84
// ExecSession contains information on a single exec session attached to a given
86
type ExecSession struct {
87
// Id is the ID of the exec session.
88
// Named somewhat strangely to not conflict with ID().
89
//nolint:stylecheck,revive
91
// ContainerId is the ID of the container this exec session belongs to.
92
// Named somewhat strangely to not conflict with ContainerID().
93
//nolint:stylecheck,revive
94
ContainerId string `json:"containerId"`
96
// State is the state of the exec session.
97
State define.ContainerExecStatus `json:"state"`
98
// PID is the PID of the process created by the exec session.
99
PID int `json:"pid,omitempty"`
100
// ExitCode is the exit code of the exec session, if it has exited.
101
ExitCode int `json:"exitCode,omitempty"`
103
// Config is the configuration of this exec session.
105
Config *ExecConfig `json:"config"`
108
// ID returns the ID of an exec session.
109
func (e *ExecSession) ID() string {
113
// ContainerID returns the ID of the container this exec session was started in.
114
func (e *ExecSession) ContainerID() string {
118
// Inspect inspects the given exec session and produces detailed output on its
119
// configuration and current state.
120
func (e *ExecSession) Inspect() (*define.InspectExecSession, error) {
122
return nil, fmt.Errorf("given exec session does not have a configuration block: %w", define.ErrInternal)
125
output := new(define.InspectExecSession)
126
output.CanRemove = e.State == define.ExecStateStopped
127
output.ContainerID = e.ContainerId
128
if e.Config.DetachKeys != nil {
129
output.DetachKeys = *e.Config.DetachKeys
131
output.ExitCode = e.ExitCode
133
output.OpenStderr = e.Config.AttachStderr
134
output.OpenStdin = e.Config.AttachStdin
135
output.OpenStdout = e.Config.AttachStdout
136
output.Running = e.State == define.ExecStateRunning
138
output.ProcessConfig = new(define.InspectExecProcess)
139
if len(e.Config.Command) > 0 {
140
output.ProcessConfig.Entrypoint = e.Config.Command[0]
141
if len(e.Config.Command) > 1 {
142
output.ProcessConfig.Arguments = make([]string, 0, len(e.Config.Command)-1)
143
output.ProcessConfig.Arguments = append(output.ProcessConfig.Arguments, e.Config.Command[1:]...)
146
output.ProcessConfig.Privileged = e.Config.Privileged
147
output.ProcessConfig.Tty = e.Config.Terminal
148
output.ProcessConfig.User = e.Config.User
153
// legacyExecSession contains information on an active exec session. It is a
154
// holdover from a previous Podman version and is DEPRECATED.
155
type legacyExecSession struct {
156
ID string `json:"id"`
157
Command []string `json:"command"`
161
// ExecCreate creates a new exec session for the container.
162
// The session is not started. The ID of the new exec session will be returned.
163
func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
166
defer c.lock.Unlock()
168
if err := c.syncContainer(); err != nil {
175
return "", fmt.Errorf("must provide a configuration to ExecCreate: %w", define.ErrInvalidArg)
177
if len(config.Command) == 0 {
178
return "", fmt.Errorf("must provide a non-empty command to start an exec session: %w", define.ErrInvalidArg)
180
if config.ExitCommandDelay > 0 && len(config.ExitCommand) == 0 {
181
return "", fmt.Errorf("must provide a non-empty exit command if giving an exit command delay: %w", define.ErrInvalidArg)
184
// Verify that we are in a good state to continue
185
if !c.ensureState(define.ContainerStateRunning) {
186
return "", fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid)
189
// Generate an ID for our new exec session
190
sessionID := stringid.GenerateRandomID()
192
// This really ought to be a do-while, but Go doesn't have those...
195
for id := range c.state.ExecSessions {
202
sessionID = stringid.GenerateRandomID()
206
// Make our new exec session
207
session := new(ExecSession)
208
session.Id = sessionID
209
session.ContainerId = c.ID()
210
session.State = define.ExecStateCreated
211
session.Config = new(ExecConfig)
212
if err := JSONDeepCopy(config, session.Config); err != nil {
213
return "", fmt.Errorf("copying exec configuration into exec session: %w", err)
216
if len(session.Config.ExitCommand) > 0 {
217
session.Config.ExitCommand = append(session.Config.ExitCommand, []string{session.ID(), c.ID()}...)
220
if c.state.ExecSessions == nil {
221
c.state.ExecSessions = make(map[string]*ExecSession)
224
// Need to add to container state and exec session registry
225
c.state.ExecSessions[session.ID()] = session
226
if err := c.save(); err != nil {
229
if err := c.runtime.state.AddExecSession(c, session); err != nil {
233
logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID())
235
return sessionID, nil
238
// ExecStart starts an exec session in the container, but does not attach to it.
239
// Returns immediately upon starting the exec session, unlike other ExecStart
240
// functions, which will only return when the exec session exits.
241
func (c *Container) ExecStart(sessionID string) error {
244
defer c.lock.Unlock()
246
if err := c.syncContainer(); err != nil {
251
// Verify that we are in a good state to continue
252
if !c.ensureState(define.ContainerStateRunning) {
253
return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid)
256
session, ok := c.state.ExecSessions[sessionID]
258
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
261
if session.State != define.ExecStateCreated {
262
return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid)
265
logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
267
opts, err := prepareForExec(c, session)
272
pid, err := c.ociRuntime.ExecContainerDetached(c, session.ID(), opts, session.Config.AttachStdin)
277
c.newContainerEvent(events.Exec)
278
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
280
// Update and save session to reflect PID/running
282
session.State = define.ExecStateRunning
287
func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize) error {
288
return c.execStartAndAttach(sessionID, streams, newSize, false)
291
// ExecStartAndAttach starts and attaches to an exec session in a container.
292
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
293
func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize, isHealthcheck bool) error {
296
defer c.lock.Unlock()
298
if err := c.syncContainer(); err != nil {
303
// Verify that we are in a good state to continue
304
if !c.ensureState(define.ContainerStateRunning) {
305
return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid)
308
session, ok := c.state.ExecSessions[sessionID]
310
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
313
if session.State != define.ExecStateCreated {
314
return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid)
317
logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
319
opts, err := prepareForExec(c, session)
324
pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, newSize)
330
c.newContainerEvent(events.Exec)
333
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
337
// Update and save session to reflect PID/running
339
session.State = define.ExecStateRunning
341
if err := c.save(); err != nil {
345
// Unlock so other processes can use the container
350
tmpErr := <-attachChan
352
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
356
exitCode, exitCodeErr := c.readExecExitCode(session.ID())
359
// Important: we must lock and sync *before* the above error is handled.
360
// We need info from the database to handle the error.
364
// We can't reuse the old exec session (things may have changed from
365
// other use, the container was unlocked).
366
// So re-sync and get a fresh copy.
367
// If we can't do this, no point in continuing, any attempt to save
368
// would write garbage to the DB.
369
if err := c.syncContainer(); err != nil {
370
if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) {
371
// We can't save status, but since the container has
372
// been entirely removed, we don't have to; exit cleanly
376
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
378
return fmt.Errorf("syncing container %s state to update exec session %s: %w", c.ID(), sessionID, err)
381
// Now handle the error from readExecExitCode above.
382
if exitCodeErr != nil {
383
newSess, ok := c.state.ExecSessions[sessionID]
385
// The exec session was removed entirely, probably by
386
// the cleanup process. When it did so, it should have
387
// written an event with the exit code.
388
// Given that, there's nothing more we can do.
389
logrus.Infof("Container %s exec session %s already removed", c.ID(), session.ID())
393
if newSess.State == define.ExecStateStopped {
394
// Exec session already cleaned up.
395
// Exit code should be recorded, so it's OK if we were
396
// not able to read it.
397
logrus.Infof("Container %s exec session %s already cleaned up", c.ID(), session.ID())
402
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
404
lastErr = exitCodeErr
407
logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode)
409
if err := justWriteExecExitCode(c, session.ID(), exitCode, !isHealthcheck); err != nil {
411
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
416
// Clean up after ourselves
417
if err := c.cleanupExecBundle(session.ID()); err != nil {
419
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
427
// ExecHTTPStartAndAttach starts and performs an HTTP attach to an exec session.
428
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
429
func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w http.ResponseWriter,
430
streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, newSize *resize.TerminalSize) error {
431
// TODO: How do we combine streams with the default streams set in the exec session?
433
// Ensure that we don't leak a goroutine here
440
defer c.lock.Unlock()
442
if err := c.syncContainer(); err != nil {
447
session, ok := c.state.ExecSessions[sessionID]
449
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
452
// Verify that we are in a good state to continue
453
if !c.ensureState(define.ContainerStateRunning) {
454
return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid)
457
if session.State != define.ExecStateCreated {
458
return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid)
461
logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
463
execOpts, err := prepareForExec(c, session)
465
session.State = define.ExecStateStopped
466
session.ExitCode = define.ExecErrorCodeGeneric
468
if err := c.save(); err != nil {
469
logrus.Errorf("Saving container %s exec session %s after failure to prepare: %v", err, c.ID(), session.ID())
476
streams = new(HTTPAttachStreams)
477
streams.Stdin = session.Config.AttachStdin
478
streams.Stdout = session.Config.AttachStdout
479
streams.Stderr = session.Config.AttachStderr
482
holdConnOpen := make(chan bool)
488
pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, r, w, streams, cancel, hijackDone, holdConnOpen, newSize)
490
session.State = define.ExecStateStopped
491
session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err)
493
if err := c.save(); err != nil {
494
logrus.Errorf("Saving container %s exec session %s after failure to start: %v", err, c.ID(), session.ID())
500
// TODO: Investigate whether more of this can be made common with
501
// ExecStartAndAttach
503
c.newContainerEvent(events.Exec)
504
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
509
session.State = define.ExecStateRunning
511
if err := c.save(); err != nil {
515
// Unlock so other processes can use the container
520
tmpErr := <-attachChan
522
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
526
exitCode, err := c.readExecExitCode(session.ID())
529
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
534
logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode)
541
if err := writeExecExitCode(c, session.ID(), exitCode); err != nil {
543
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
548
// Clean up after ourselves
549
if err := c.cleanupExecBundle(session.ID()); err != nil {
551
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
559
// ExecStop stops an exec session in the container.
560
// If a timeout is provided, it will be used; otherwise, the timeout will
561
// default to the stop timeout of the container.
562
// Cleanup will be invoked automatically once the session is stopped.
563
func (c *Container) ExecStop(sessionID string, timeout *uint) error {
566
defer c.lock.Unlock()
568
if err := c.syncContainer(); err != nil {
573
session, ok := c.state.ExecSessions[sessionID]
575
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
578
if session.State != define.ExecStateRunning {
579
return fmt.Errorf("container %s exec session %s is %q, can only stop running sessions: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid)
582
logrus.Infof("Stopping container %s exec session %s", c.ID(), session.ID())
584
finalTimeout := c.StopTimeout()
586
finalTimeout = *timeout
590
if err := c.ociRuntime.ExecStopContainer(c, session.ID(), finalTimeout); err != nil {
596
// Retrieve exit code and update status
597
if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
601
if err := c.cleanupExecBundle(session.ID()); err != nil {
602
if cleanupErr != nil {
603
logrus.Errorf("Stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr)
611
// ExecCleanup cleans up an exec session in the container, removing temporary
612
// files associated with it.
613
func (c *Container) ExecCleanup(sessionID string) error {
616
defer c.lock.Unlock()
618
if err := c.syncContainer(); err != nil {
623
session, ok := c.state.ExecSessions[sessionID]
625
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
628
if session.State == define.ExecStateRunning {
629
// Check if the exec session is still running.
630
alive, err := c.ociRuntime.ExecUpdateStatus(c, session.ID())
636
return fmt.Errorf("cannot clean up container %s exec session %s as it is running: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
639
if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
644
logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID())
646
return c.cleanupExecBundle(session.ID())
649
// ExecRemove removes an exec session in the container.
650
// If force is given, the session will be stopped first if it is running.
651
func (c *Container) ExecRemove(sessionID string, force bool) error {
654
defer c.lock.Unlock()
656
if err := c.syncContainer(); err != nil {
661
session, ok := c.state.ExecSessions[sessionID]
663
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
666
logrus.Infof("Removing container %s exec session %s", c.ID(), session.ID())
668
// Update status of exec session if running, so we can check if it
669
// stopped in the meantime.
670
if session.State == define.ExecStateRunning {
671
running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID())
676
if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
682
if session.State == define.ExecStateRunning {
684
return fmt.Errorf("container %s exec session %s is still running, cannot remove: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
688
if err := c.ociRuntime.ExecStopContainer(c, session.ID(), c.StopTimeout()); err != nil {
692
if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
696
if err := c.cleanupExecBundle(session.ID()); err != nil {
701
// First remove exec session from DB.
702
if err := c.runtime.state.RemoveExecSession(session); err != nil {
705
// Next, remove it from the container and save state
706
delete(c.state.ExecSessions, sessionID)
707
if err := c.save(); err != nil {
711
logrus.Debugf("Successfully removed container %s exec session %s", c.ID(), session.ID())
716
// ExecResize resizes the TTY of the given exec session. Only available if the
717
// exec session created a TTY.
718
func (c *Container) ExecResize(sessionID string, newSize resize.TerminalSize) error {
721
defer c.lock.Unlock()
723
if err := c.syncContainer(); err != nil {
728
session, ok := c.state.ExecSessions[sessionID]
730
return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
733
logrus.Infof("Resizing container %s exec session %s to %+v", c.ID(), session.ID(), newSize)
735
if session.State != define.ExecStateRunning {
736
return fmt.Errorf("cannot resize container %s exec session %s as it is not running: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
739
// The exec session may have exited since we last updated.
740
// Needed to prevent race conditions around short-running exec sessions.
741
running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID())
746
session.State = define.ExecStateStopped
748
if err := c.save(); err != nil {
749
logrus.Errorf("Saving state of container %s: %v", c.ID(), err)
752
return fmt.Errorf("cannot resize container %s exec session %s as it has stopped: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
755
// Make sure the exec session is still running.
757
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
760
func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan resize.TerminalSize) (int, error) {
761
return c.exec(config, streams, resize, false)
764
// Exec emulates the old Libpod exec API, providing a single call to create,
765
// run, and remove an exec session. Returns exit code and error. Exit code is
766
// not guaranteed to be set sanely if error is not nil.
767
func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resizeChan <-chan resize.TerminalSize, isHealthcheck bool) (exitCode int, retErr error) {
768
sessionID, err := c.ExecCreate(config)
773
if err := c.ExecRemove(sessionID, false); err != nil {
774
if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
781
// Start resizing if we have a resize channel.
782
// This goroutine may likely leak, given that we cannot close it here.
783
// Not a big deal, since it should run for as long as the Podman process
784
// does. Could be a big deal for `podman service` but we don't need this
786
// TODO: Refactor so this is closed here, before we remove the exec
788
var size *resize.TerminalSize
789
if resizeChan != nil {
793
logrus.Debugf("Sending resize events to exec session %s", sessionID)
794
for resizeRequest := range resizeChan {
795
if err := c.ExecResize(sessionID, resizeRequest); err != nil {
796
if errors.Is(err, define.ErrExecSessionStateInvalid) {
797
// The exec session stopped
798
// before we could resize.
799
logrus.Infof("Missed resize on exec session %s, already stopped", sessionID)
801
logrus.Warnf("Error resizing exec session %s: %v", sessionID, err)
809
if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
813
session, err := c.execSessionNoCopy(sessionID)
815
if errors.Is(err, define.ErrNoSuchExecSession) {
816
// TODO: If a proper Context is ever plumbed in here, we
818
// As things stand, though, it's not worth it - this
819
// should always terminate quickly since it's not
821
diedEvent, err := c.runtime.GetExecDiedEvent(context.Background(), c.ID(), sessionID)
823
return -1, fmt.Errorf("retrieving exec session %s exit code: %w", sessionID, err)
825
return *diedEvent.ContainerExitCode, nil
829
return session.ExitCode, nil
832
// cleanupExecBundle cleanups an exec session after its done
833
// Please be careful when using this function since it might temporarily unlock
834
// the container when os.RemoveAll($bundlePath) fails with ENOTEMPTY or EBUSY
836
func (c *Container) cleanupExecBundle(sessionID string) (err error) {
837
path := c.execBundlePath(sessionID)
838
for attempts := 0; attempts < 50; attempts++ {
839
err = os.RemoveAll(path)
840
if err == nil || os.IsNotExist(err) {
843
if pathErr, ok := err.(*os.PathError); ok {
845
if errors.Is(err, unix.ENOTEMPTY) || errors.Is(err, unix.EBUSY) {
846
// give other processes a chance to use the container
848
if err := c.save(); err != nil {
853
time.Sleep(time.Millisecond * 100)
856
if err := c.syncContainer(); err != nil {
868
// the path to a container's exec session bundle
869
func (c *Container) execBundlePath(sessionID string) string {
870
return filepath.Join(c.bundlePath(), sessionID)
873
// Get PID file path for a container's exec session
874
func (c *Container) execPidPath(sessionID string) string {
875
return filepath.Join(c.execBundlePath(sessionID), "exec_pid")
878
// the log path for an exec session
879
func (c *Container) execLogPath(sessionID string) string {
880
return filepath.Join(c.execBundlePath(sessionID), "exec_log")
883
// the socket conmon creates for an exec session
884
func (c *Container) execAttachSocketPath(sessionID string) (string, error) {
885
return c.ociRuntime.ExecAttachSocketPath(c, sessionID)
888
// execExitFileDir gets the path to the container's exit file
889
func (c *Container) execExitFileDir(sessionID string) string {
890
return filepath.Join(c.execBundlePath(sessionID), "exit")
893
// execPersistDir gets the path to the container's persist directory
894
// The persist directory container the exit file and oom file (if oomkilled)
896
func (c *Container) execPersistDir(sessionID string) string {
897
return filepath.Join(c.execBundlePath(sessionID), "persist", c.ID())
900
// execOCILog returns the file path for the exec sessions oci log
901
func (c *Container) execOCILog(sessionID string) string {
902
if !c.ociRuntime.SupportsJSONErrors() {
905
return filepath.Join(c.execBundlePath(sessionID), "oci-log")
908
// create a bundle path and associated files for an exec session
909
func (c *Container) createExecBundle(sessionID string) (retErr error) {
910
bundlePath := c.execBundlePath(sessionID)
911
if err := os.MkdirAll(bundlePath, execDirPermission); err != nil {
916
if err := os.RemoveAll(bundlePath); err != nil {
917
logrus.Warnf("Error removing exec bundle after creation caused another error: %v", err)
921
if err := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err != nil {
922
// The directory is allowed to exist
923
if !os.IsExist(err) {
924
return fmt.Errorf("creating OCI runtime exit file path %s: %w", c.execExitFileDir(sessionID), err)
927
if err := os.MkdirAll(c.execPersistDir(sessionID), execDirPermission); err != nil {
928
return fmt.Errorf("creating OCI runtime persist directory path %s: %w", c.execPersistDir(sessionID), err)
933
// readExecExitCode reads the exit file for an exec session and returns
935
func (c *Container) readExecExitCode(sessionID string) (int, error) {
936
exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
937
chWait := make(chan error)
940
_, err := util.WaitForFile(exitFile, chWait, time.Second*5)
944
ec, err := os.ReadFile(exitFile)
948
ecInt, err := strconv.Atoi(string(ec))
955
// getExecSessionPID gets the PID of an active exec session
956
func (c *Container) getExecSessionPID(sessionID string) (int, error) {
957
session, ok := c.state.ExecSessions[sessionID]
959
return session.PID, nil
961
oldSession, ok := c.state.LegacyExecSessions[sessionID]
963
return oldSession.PID, nil
966
return -1, fmt.Errorf("no exec session with ID %s found in container %s: %w", sessionID, c.ID(), define.ErrNoSuchExecSession)
969
// getKnownExecSessions gets a list of all exec sessions we think are running,
970
// but does not verify their current state.
971
// Please use getActiveExecSessions() outside of container_exec.go, as this
972
// function performs further checks to return an accurate list.
973
func (c *Container) getKnownExecSessions() []string {
974
knownSessions := []string{}
975
// First check legacy sessions.
976
// TODO: This is DEPRECATED and will be removed in a future major
978
for sessionID := range c.state.LegacyExecSessions {
979
knownSessions = append(knownSessions, sessionID)
981
// Next check new exec sessions, but only if in running state
982
for sessionID, session := range c.state.ExecSessions {
983
if session.State == define.ExecStateRunning {
984
knownSessions = append(knownSessions, sessionID)
991
// getActiveExecSessions checks if there are any active exec sessions in the
992
// current container. Returns an array of active exec sessions.
993
// Will continue through errors where possible.
994
// Currently handles both new and legacy, deprecated exec sessions.
995
func (c *Container) getActiveExecSessions() ([]string, error) {
996
activeSessions := []string{}
997
knownSessions := c.getKnownExecSessions()
999
// Instead of saving once per iteration, do it once at the end.
1002
for _, id := range knownSessions {
1003
alive, err := c.ociRuntime.ExecUpdateStatus(c, id)
1006
logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1012
_, isLegacy := c.state.LegacyExecSessions[id]
1014
delete(c.state.LegacyExecSessions, id)
1017
session := c.state.ExecSessions[id]
1018
exitCode, err := c.readExecExitCode(session.ID())
1021
logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1025
session.ExitCode = exitCode
1027
session.State = define.ExecStateStopped
1029
c.newExecDiedEvent(session.ID(), exitCode)
1033
if err := c.cleanupExecBundle(id); err != nil {
1035
logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1040
activeSessions = append(activeSessions, id)
1044
if err := c.save(); err != nil {
1046
logrus.Errorf("Reaping exec sessions for container %s: %v", c.ID(), lastErr)
1052
return activeSessions, lastErr
1055
// removeAllExecSessions stops and removes all the container's exec sessions
1056
func (c *Container) removeAllExecSessions() error {
1057
knownSessions := c.getKnownExecSessions()
1059
logrus.Debugf("Removing all exec sessions for container %s", c.ID())
1062
for _, id := range knownSessions {
1063
if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil {
1065
logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1071
if err := c.cleanupExecBundle(id); err != nil {
1073
logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1078
// Delete all exec sessions
1079
if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil {
1080
if !errors.Is(err, define.ErrCtrRemoved) {
1082
logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1087
c.state.ExecSessions = nil
1088
c.state.LegacyExecSessions = nil
1093
// Make an ExecOptions struct to start the OCI runtime and prepare its exec
1095
func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
1096
if err := c.createExecBundle(session.ID()); err != nil {
1100
opts := new(ExecOptions)
1101
opts.Cmd = session.Config.Command
1102
opts.Env = session.Config.Environment
1103
opts.Terminal = session.Config.Terminal
1104
opts.Cwd = session.Config.WorkDir
1105
opts.User = session.Config.User
1106
opts.PreserveFDs = session.Config.PreserveFDs
1107
opts.PreserveFD = session.Config.PreserveFD
1108
opts.DetachKeys = session.Config.DetachKeys
1109
opts.ExitCommand = session.Config.ExitCommand
1110
opts.ExitCommandDelay = session.Config.ExitCommandDelay
1111
opts.Privileged = session.Config.Privileged
1116
// Write an exec session's exit code to the database
1117
func writeExecExitCode(c *Container, sessionID string, exitCode int) error {
1118
// We can't reuse the old exec session (things may have changed from
1119
// under use, the container was unlocked).
1120
// So re-sync and get a fresh copy.
1121
// If we can't do this, no point in continuing, any attempt to save
1122
// would write garbage to the DB.
1123
if err := c.syncContainer(); err != nil {
1124
if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) {
1125
// Container's entirely removed. We can't save status,
1126
// but the container's entirely removed, so we don't
1127
// need to. Exit without error.
1130
return fmt.Errorf("syncing container %s state to remove exec session %s: %w", c.ID(), sessionID, err)
1133
return justWriteExecExitCode(c, sessionID, exitCode, true)
1136
func retrieveAndWriteExecExitCode(c *Container, sessionID string) error {
1137
exitCode, err := c.readExecExitCode(sessionID)
1142
return justWriteExecExitCode(c, sessionID, exitCode, true)
1145
func justWriteExecExitCode(c *Container, sessionID string, exitCode int, emitEvent bool) error {
1146
// Write an event first
1148
c.newExecDiedEvent(sessionID, exitCode)
1151
session, ok := c.state.ExecSessions[sessionID]
1153
// Exec session already removed.
1154
logrus.Infof("Container %s exec session %s already removed from database", c.ID(), sessionID)
1158
session.State = define.ExecStateStopped
1159
session.ExitCode = exitCode
1162
// Finally, save our changes.