podman

Форк
0
/
container_exec.go 
1164 строки · 36.6 Кб
1
//go:build !remote
2

3
package libpod
4

5
import (
6
	"context"
7
	"errors"
8
	"fmt"
9
	"net/http"
10
	"os"
11
	"path/filepath"
12
	"strconv"
13
	"time"
14

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"
22
)
23

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.
27
	// Must not be empty.
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
33
	// false.
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
37
	// Terminal is false.
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
41
	// Terminal is false.
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
60
	// container.
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,
66
	// starting at 3.
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"`
82
}
83

84
// ExecSession contains information on a single exec session attached to a given
85
// container.
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
90
	Id string `json:"id"`
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"`
95

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"`
102

103
	// Config is the configuration of this exec session.
104
	// Cannot be empty.
105
	Config *ExecConfig `json:"config"`
106
}
107

108
// ID returns the ID of an exec session.
109
func (e *ExecSession) ID() string {
110
	return e.Id
111
}
112

113
// ContainerID returns the ID of the container this exec session was started in.
114
func (e *ExecSession) ContainerID() string {
115
	return e.ContainerId
116
}
117

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) {
121
	if e.Config == nil {
122
		return nil, fmt.Errorf("given exec session does not have a configuration block: %w", define.ErrInternal)
123
	}
124

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
130
	}
131
	output.ExitCode = e.ExitCode
132
	output.ID = e.Id
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
137
	output.Pid = e.PID
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:]...)
144
		}
145
	}
146
	output.ProcessConfig.Privileged = e.Config.Privileged
147
	output.ProcessConfig.Tty = e.Config.Terminal
148
	output.ProcessConfig.User = e.Config.User
149

150
	return output, nil
151
}
152

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"`
158
	PID     int      `json:"pid"`
159
}
160

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) {
164
	if !c.batched {
165
		c.lock.Lock()
166
		defer c.lock.Unlock()
167

168
		if err := c.syncContainer(); err != nil {
169
			return "", err
170
		}
171
	}
172

173
	// Verify our config
174
	if config == nil {
175
		return "", fmt.Errorf("must provide a configuration to ExecCreate: %w", define.ErrInvalidArg)
176
	}
177
	if len(config.Command) == 0 {
178
		return "", fmt.Errorf("must provide a non-empty command to start an exec session: %w", define.ErrInvalidArg)
179
	}
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)
182
	}
183

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)
187
	}
188

189
	// Generate an ID for our new exec session
190
	sessionID := stringid.GenerateRandomID()
191
	found := true
192
	// This really ought to be a do-while, but Go doesn't have those...
193
	for found {
194
		found = false
195
		for id := range c.state.ExecSessions {
196
			if id == sessionID {
197
				found = true
198
				break
199
			}
200
		}
201
		if found {
202
			sessionID = stringid.GenerateRandomID()
203
		}
204
	}
205

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)
214
	}
215

216
	if len(session.Config.ExitCommand) > 0 {
217
		session.Config.ExitCommand = append(session.Config.ExitCommand, []string{session.ID(), c.ID()}...)
218
	}
219

220
	if c.state.ExecSessions == nil {
221
		c.state.ExecSessions = make(map[string]*ExecSession)
222
	}
223

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 {
227
		return "", err
228
	}
229
	if err := c.runtime.state.AddExecSession(c, session); err != nil {
230
		return "", err
231
	}
232

233
	logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID())
234

235
	return sessionID, nil
236
}
237

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 {
242
	if !c.batched {
243
		c.lock.Lock()
244
		defer c.lock.Unlock()
245

246
		if err := c.syncContainer(); err != nil {
247
			return err
248
		}
249
	}
250

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)
254
	}
255

256
	session, ok := c.state.ExecSessions[sessionID]
257
	if !ok {
258
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
259
	}
260

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)
263
	}
264

265
	logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
266

267
	opts, err := prepareForExec(c, session)
268
	if err != nil {
269
		return err
270
	}
271

272
	pid, err := c.ociRuntime.ExecContainerDetached(c, session.ID(), opts, session.Config.AttachStdin)
273
	if err != nil {
274
		return err
275
	}
276

277
	c.newContainerEvent(events.Exec)
278
	logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
279

280
	// Update and save session to reflect PID/running
281
	session.PID = pid
282
	session.State = define.ExecStateRunning
283

284
	return c.save()
285
}
286

287
func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize) error {
288
	return c.execStartAndAttach(sessionID, streams, newSize, false)
289
}
290

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 {
294
	if !c.batched {
295
		c.lock.Lock()
296
		defer c.lock.Unlock()
297

298
		if err := c.syncContainer(); err != nil {
299
			return err
300
		}
301
	}
302

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)
306
	}
307

308
	session, ok := c.state.ExecSessions[sessionID]
309
	if !ok {
310
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
311
	}
312

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)
315
	}
316

317
	logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
318

319
	opts, err := prepareForExec(c, session)
320
	if err != nil {
321
		return err
322
	}
323

324
	pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, newSize)
325
	if err != nil {
326
		return err
327
	}
328

329
	if !isHealthcheck {
330
		c.newContainerEvent(events.Exec)
331
	}
332

333
	logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
334

335
	var lastErr error
336

337
	// Update and save session to reflect PID/running
338
	session.PID = pid
339
	session.State = define.ExecStateRunning
340

341
	if err := c.save(); err != nil {
342
		lastErr = err
343
	}
344

345
	// Unlock so other processes can use the container
346
	if !c.batched {
347
		c.lock.Unlock()
348
	}
349

350
	tmpErr := <-attachChan
351
	if lastErr != nil {
352
		logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
353
	}
354
	lastErr = tmpErr
355

356
	exitCode, exitCodeErr := c.readExecExitCode(session.ID())
357

358
	// Lock again.
359
	// Important: we must lock and sync *before* the above error is handled.
360
	// We need info from the database to handle the error.
361
	if !c.batched {
362
		c.lock.Lock()
363
	}
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
373
			return lastErr
374
		}
375
		if lastErr != nil {
376
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
377
		}
378
		return fmt.Errorf("syncing container %s state to update exec session %s: %w", c.ID(), sessionID, err)
379
	}
380

381
	// Now handle the error from readExecExitCode above.
382
	if exitCodeErr != nil {
383
		newSess, ok := c.state.ExecSessions[sessionID]
384
		if !ok {
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())
390
			return lastErr
391
		}
392

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())
398
			return lastErr
399
		}
400

401
		if lastErr != nil {
402
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
403
		}
404
		lastErr = exitCodeErr
405
	}
406

407
	logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode)
408

409
	if err := justWriteExecExitCode(c, session.ID(), exitCode, !isHealthcheck); err != nil {
410
		if lastErr != nil {
411
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
412
		}
413
		lastErr = err
414
	}
415

416
	// Clean up after ourselves
417
	if err := c.cleanupExecBundle(session.ID()); err != nil {
418
		if lastErr != nil {
419
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
420
		}
421
		lastErr = err
422
	}
423

424
	return lastErr
425
}
426

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?
432

433
	// Ensure that we don't leak a goroutine here
434
	defer func() {
435
		close(hijackDone)
436
	}()
437

438
	if !c.batched {
439
		c.lock.Lock()
440
		defer c.lock.Unlock()
441

442
		if err := c.syncContainer(); err != nil {
443
			return err
444
		}
445
	}
446

447
	session, ok := c.state.ExecSessions[sessionID]
448
	if !ok {
449
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
450
	}
451

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)
455
	}
456

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)
459
	}
460

461
	logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
462

463
	execOpts, err := prepareForExec(c, session)
464
	if err != nil {
465
		session.State = define.ExecStateStopped
466
		session.ExitCode = define.ExecErrorCodeGeneric
467

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())
470
		}
471

472
		return err
473
	}
474

475
	if streams == nil {
476
		streams = new(HTTPAttachStreams)
477
		streams.Stdin = session.Config.AttachStdin
478
		streams.Stdout = session.Config.AttachStdout
479
		streams.Stderr = session.Config.AttachStderr
480
	}
481

482
	holdConnOpen := make(chan bool)
483

484
	defer func() {
485
		close(holdConnOpen)
486
	}()
487

488
	pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, r, w, streams, cancel, hijackDone, holdConnOpen, newSize)
489
	if err != nil {
490
		session.State = define.ExecStateStopped
491
		session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err)
492

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())
495
		}
496

497
		return err
498
	}
499

500
	// TODO: Investigate whether more of this can be made common with
501
	// ExecStartAndAttach
502

503
	c.newContainerEvent(events.Exec)
504
	logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
505

506
	var lastErr error
507

508
	session.PID = pid
509
	session.State = define.ExecStateRunning
510

511
	if err := c.save(); err != nil {
512
		lastErr = err
513
	}
514

515
	// Unlock so other processes can use the container
516
	if !c.batched {
517
		c.lock.Unlock()
518
	}
519

520
	tmpErr := <-attachChan
521
	if lastErr != nil {
522
		logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
523
	}
524
	lastErr = tmpErr
525

526
	exitCode, err := c.readExecExitCode(session.ID())
527
	if err != nil {
528
		if lastErr != nil {
529
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
530
		}
531
		lastErr = err
532
	}
533

534
	logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode)
535

536
	// Lock again
537
	if !c.batched {
538
		c.lock.Lock()
539
	}
540

541
	if err := writeExecExitCode(c, session.ID(), exitCode); err != nil {
542
		if lastErr != nil {
543
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
544
		}
545
		lastErr = err
546
	}
547

548
	// Clean up after ourselves
549
	if err := c.cleanupExecBundle(session.ID()); err != nil {
550
		if lastErr != nil {
551
			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
552
		}
553
		lastErr = err
554
	}
555

556
	return lastErr
557
}
558

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 {
564
	if !c.batched {
565
		c.lock.Lock()
566
		defer c.lock.Unlock()
567

568
		if err := c.syncContainer(); err != nil {
569
			return err
570
		}
571
	}
572

573
	session, ok := c.state.ExecSessions[sessionID]
574
	if !ok {
575
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
576
	}
577

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)
580
	}
581

582
	logrus.Infof("Stopping container %s exec session %s", c.ID(), session.ID())
583

584
	finalTimeout := c.StopTimeout()
585
	if timeout != nil {
586
		finalTimeout = *timeout
587
	}
588

589
	// Stop the session
590
	if err := c.ociRuntime.ExecStopContainer(c, session.ID(), finalTimeout); err != nil {
591
		return err
592
	}
593

594
	var cleanupErr error
595

596
	// Retrieve exit code and update status
597
	if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
598
		cleanupErr = err
599
	}
600

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)
604
		}
605
		cleanupErr = err
606
	}
607

608
	return cleanupErr
609
}
610

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 {
614
	if !c.batched {
615
		c.lock.Lock()
616
		defer c.lock.Unlock()
617

618
		if err := c.syncContainer(); err != nil {
619
			return err
620
		}
621
	}
622

623
	session, ok := c.state.ExecSessions[sessionID]
624
	if !ok {
625
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
626
	}
627

628
	if session.State == define.ExecStateRunning {
629
		// Check if the exec session is still running.
630
		alive, err := c.ociRuntime.ExecUpdateStatus(c, session.ID())
631
		if err != nil {
632
			return err
633
		}
634

635
		if alive {
636
			return fmt.Errorf("cannot clean up container %s exec session %s as it is running: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
637
		}
638

639
		if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
640
			return err
641
		}
642
	}
643

644
	logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID())
645

646
	return c.cleanupExecBundle(session.ID())
647
}
648

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 {
652
	if !c.batched {
653
		c.lock.Lock()
654
		defer c.lock.Unlock()
655

656
		if err := c.syncContainer(); err != nil {
657
			return err
658
		}
659
	}
660

661
	session, ok := c.state.ExecSessions[sessionID]
662
	if !ok {
663
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
664
	}
665

666
	logrus.Infof("Removing container %s exec session %s", c.ID(), session.ID())
667

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())
672
		if err != nil {
673
			return err
674
		}
675
		if !running {
676
			if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
677
				return err
678
			}
679
		}
680
	}
681

682
	if session.State == define.ExecStateRunning {
683
		if !force {
684
			return fmt.Errorf("container %s exec session %s is still running, cannot remove: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
685
		}
686

687
		// Stop the session
688
		if err := c.ociRuntime.ExecStopContainer(c, session.ID(), c.StopTimeout()); err != nil {
689
			return err
690
		}
691

692
		if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil {
693
			return err
694
		}
695

696
		if err := c.cleanupExecBundle(session.ID()); err != nil {
697
			return err
698
		}
699
	}
700

701
	// First remove exec session from DB.
702
	if err := c.runtime.state.RemoveExecSession(session); err != nil {
703
		return err
704
	}
705
	// Next, remove it from the container and save state
706
	delete(c.state.ExecSessions, sessionID)
707
	if err := c.save(); err != nil {
708
		return err
709
	}
710

711
	logrus.Debugf("Successfully removed container %s exec session %s", c.ID(), session.ID())
712

713
	return nil
714
}
715

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 {
719
	if !c.batched {
720
		c.lock.Lock()
721
		defer c.lock.Unlock()
722

723
		if err := c.syncContainer(); err != nil {
724
			return err
725
		}
726
	}
727

728
	session, ok := c.state.ExecSessions[sessionID]
729
	if !ok {
730
		return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession)
731
	}
732

733
	logrus.Infof("Resizing container %s exec session %s to %+v", c.ID(), session.ID(), newSize)
734

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)
737
	}
738

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())
742
	if err != nil {
743
		return err
744
	}
745
	if !running {
746
		session.State = define.ExecStateStopped
747

748
		if err := c.save(); err != nil {
749
			logrus.Errorf("Saving state of container %s: %v", c.ID(), err)
750
		}
751

752
		return fmt.Errorf("cannot resize container %s exec session %s as it has stopped: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid)
753
	}
754

755
	// Make sure the exec session is still running.
756

757
	return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
758
}
759

760
func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan resize.TerminalSize) (int, error) {
761
	return c.exec(config, streams, resize, false)
762
}
763

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)
769
	if err != nil {
770
		return -1, err
771
	}
772
	defer func() {
773
		if err := c.ExecRemove(sessionID, false); err != nil {
774
			if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
775
				exitCode = -1
776
				retErr = err
777
			}
778
		}
779
	}()
780

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
785
	// API there.
786
	// TODO: Refactor so this is closed here, before we remove the exec
787
	// session.
788
	var size *resize.TerminalSize
789
	if resizeChan != nil {
790
		s := <-resizeChan
791
		size = &s
792
		go func() {
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)
800
					} else {
801
						logrus.Warnf("Error resizing exec session %s: %v", sessionID, err)
802
					}
803
					return
804
				}
805
			}
806
		}()
807
	}
808

809
	if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
810
		return -1, err
811
	}
812

813
	session, err := c.execSessionNoCopy(sessionID)
814
	if err != nil {
815
		if errors.Is(err, define.ErrNoSuchExecSession) {
816
			// TODO: If a proper Context is ever plumbed in here, we
817
			// should use it.
818
			// As things stand, though, it's not worth it - this
819
			// should always terminate quickly since it's not
820
			// streaming.
821
			diedEvent, err := c.runtime.GetExecDiedEvent(context.Background(), c.ID(), sessionID)
822
			if err != nil {
823
				return -1, fmt.Errorf("retrieving exec session %s exit code: %w", sessionID, err)
824
			}
825
			return *diedEvent.ContainerExitCode, nil
826
		}
827
		return -1, err
828
	}
829
	return session.ExitCode, nil
830
}
831

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
835
// errors.
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) {
841
			return nil
842
		}
843
		if pathErr, ok := err.(*os.PathError); ok {
844
			err = pathErr.Err
845
			if errors.Is(err, unix.ENOTEMPTY) || errors.Is(err, unix.EBUSY) {
846
				// give other processes a chance to use the container
847
				if !c.batched {
848
					if err := c.save(); err != nil {
849
						return err
850
					}
851
					c.lock.Unlock()
852
				}
853
				time.Sleep(time.Millisecond * 100)
854
				if !c.batched {
855
					c.lock.Lock()
856
					if err := c.syncContainer(); err != nil {
857
						return err
858
					}
859
				}
860
				continue
861
			}
862
		}
863
		return
864
	}
865
	return
866
}
867

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)
871
}
872

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")
876
}
877

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")
881
}
882

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)
886
}
887

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")
891
}
892

893
// execPersistDir gets the path to the container's persist directory
894
// The persist directory container the exit file and oom file (if oomkilled)
895
// of a container
896
func (c *Container) execPersistDir(sessionID string) string {
897
	return filepath.Join(c.execBundlePath(sessionID), "persist", c.ID())
898
}
899

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() {
903
		return ""
904
	}
905
	return filepath.Join(c.execBundlePath(sessionID), "oci-log")
906
}
907

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 {
912
		return err
913
	}
914
	defer func() {
915
		if retErr != nil {
916
			if err := os.RemoveAll(bundlePath); err != nil {
917
				logrus.Warnf("Error removing exec bundle after creation caused another error: %v", err)
918
			}
919
		}
920
	}()
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)
925
		}
926
	}
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)
929
	}
930
	return nil
931
}
932

933
// readExecExitCode reads the exit file for an exec session and returns
934
// the exit code
935
func (c *Container) readExecExitCode(sessionID string) (int, error) {
936
	exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
937
	chWait := make(chan error)
938
	defer close(chWait)
939

940
	_, err := util.WaitForFile(exitFile, chWait, time.Second*5)
941
	if err != nil {
942
		return -1, err
943
	}
944
	ec, err := os.ReadFile(exitFile)
945
	if err != nil {
946
		return -1, err
947
	}
948
	ecInt, err := strconv.Atoi(string(ec))
949
	if err != nil {
950
		return -1, err
951
	}
952
	return ecInt, nil
953
}
954

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]
958
	if ok {
959
		return session.PID, nil
960
	}
961
	oldSession, ok := c.state.LegacyExecSessions[sessionID]
962
	if ok {
963
		return oldSession.PID, nil
964
	}
965

966
	return -1, fmt.Errorf("no exec session with ID %s found in container %s: %w", sessionID, c.ID(), define.ErrNoSuchExecSession)
967
}
968

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
977
	// release.
978
	for sessionID := range c.state.LegacyExecSessions {
979
		knownSessions = append(knownSessions, sessionID)
980
	}
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)
985
		}
986
	}
987

988
	return knownSessions
989
}
990

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()
998

999
	// Instead of saving once per iteration, do it once at the end.
1000
	var lastErr error
1001
	needSave := false
1002
	for _, id := range knownSessions {
1003
		alive, err := c.ociRuntime.ExecUpdateStatus(c, id)
1004
		if err != nil {
1005
			if lastErr != nil {
1006
				logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1007
			}
1008
			lastErr = err
1009
			continue
1010
		}
1011
		if !alive {
1012
			_, isLegacy := c.state.LegacyExecSessions[id]
1013
			if isLegacy {
1014
				delete(c.state.LegacyExecSessions, id)
1015
				needSave = true
1016
			} else {
1017
				session := c.state.ExecSessions[id]
1018
				exitCode, err := c.readExecExitCode(session.ID())
1019
				if err != nil {
1020
					if lastErr != nil {
1021
						logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1022
					}
1023
					lastErr = err
1024
				}
1025
				session.ExitCode = exitCode
1026
				session.PID = 0
1027
				session.State = define.ExecStateStopped
1028

1029
				c.newExecDiedEvent(session.ID(), exitCode)
1030

1031
				needSave = true
1032
			}
1033
			if err := c.cleanupExecBundle(id); err != nil {
1034
				if lastErr != nil {
1035
					logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr)
1036
				}
1037
				lastErr = err
1038
			}
1039
		} else {
1040
			activeSessions = append(activeSessions, id)
1041
		}
1042
	}
1043
	if needSave {
1044
		if err := c.save(); err != nil {
1045
			if lastErr != nil {
1046
				logrus.Errorf("Reaping exec sessions for container %s: %v", c.ID(), lastErr)
1047
			}
1048
			lastErr = err
1049
		}
1050
	}
1051

1052
	return activeSessions, lastErr
1053
}
1054

1055
// removeAllExecSessions stops and removes all the container's exec sessions
1056
func (c *Container) removeAllExecSessions() error {
1057
	knownSessions := c.getKnownExecSessions()
1058

1059
	logrus.Debugf("Removing all exec sessions for container %s", c.ID())
1060

1061
	var lastErr error
1062
	for _, id := range knownSessions {
1063
		if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil {
1064
			if lastErr != nil {
1065
				logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1066
			}
1067
			lastErr = err
1068
			continue
1069
		}
1070

1071
		if err := c.cleanupExecBundle(id); err != nil {
1072
			if lastErr != nil {
1073
				logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1074
			}
1075
			lastErr = err
1076
		}
1077
	}
1078
	// Delete all exec sessions
1079
	if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil {
1080
		if !errors.Is(err, define.ErrCtrRemoved) {
1081
			if lastErr != nil {
1082
				logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr)
1083
			}
1084
			lastErr = err
1085
		}
1086
	}
1087
	c.state.ExecSessions = nil
1088
	c.state.LegacyExecSessions = nil
1089

1090
	return lastErr
1091
}
1092

1093
// Make an ExecOptions struct to start the OCI runtime and prepare its exec
1094
// bundle.
1095
func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
1096
	if err := c.createExecBundle(session.ID()); err != nil {
1097
		return nil, err
1098
	}
1099

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
1112

1113
	return opts, nil
1114
}
1115

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.
1128
			return nil
1129
		}
1130
		return fmt.Errorf("syncing container %s state to remove exec session %s: %w", c.ID(), sessionID, err)
1131
	}
1132

1133
	return justWriteExecExitCode(c, sessionID, exitCode, true)
1134
}
1135

1136
func retrieveAndWriteExecExitCode(c *Container, sessionID string) error {
1137
	exitCode, err := c.readExecExitCode(sessionID)
1138
	if err != nil {
1139
		return err
1140
	}
1141

1142
	return justWriteExecExitCode(c, sessionID, exitCode, true)
1143
}
1144

1145
func justWriteExecExitCode(c *Container, sessionID string, exitCode int, emitEvent bool) error {
1146
	// Write an event first
1147
	if emitEvent {
1148
		c.newExecDiedEvent(sessionID, exitCode)
1149
	}
1150

1151
	session, ok := c.state.ExecSessions[sessionID]
1152
	if !ok {
1153
		// Exec session already removed.
1154
		logrus.Infof("Container %s exec session %s already removed from database", c.ID(), sessionID)
1155
		return nil
1156
	}
1157

1158
	session.State = define.ExecStateStopped
1159
	session.ExitCode = exitCode
1160
	session.PID = 0
1161

1162
	// Finally, save our changes.
1163
	return c.save()
1164
}
1165

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.