podman

Форк
0
/
oci_conmon_exec_common.go 
779 строк · 23.2 Кб
1
//go:build !remote && (linux || freebsd)
2

3
package libpod
4

5
import (
6
	"errors"
7
	"fmt"
8
	"net/http"
9
	"os"
10
	"os/exec"
11
	"path/filepath"
12
	"strconv"
13
	"strings"
14
	"syscall"
15
	"time"
16

17
	"github.com/containers/common/pkg/config"
18
	"github.com/containers/common/pkg/detach"
19
	"github.com/containers/common/pkg/resize"
20
	"github.com/containers/podman/v5/libpod/define"
21
	"github.com/containers/podman/v5/pkg/errorhandling"
22
	"github.com/containers/podman/v5/pkg/lookup"
23
	spec "github.com/opencontainers/runtime-spec/specs-go"
24
	"github.com/sirupsen/logrus"
25
	"golang.org/x/sys/unix"
26
)
27

28
// ExecContainer executes a command in a running container
29
func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *resize.TerminalSize) (int, chan error, error) {
30
	if options == nil {
31
		return -1, nil, fmt.Errorf("must provide an ExecOptions struct to ExecContainer: %w", define.ErrInvalidArg)
32
	}
33
	if len(options.Cmd) == 0 {
34
		return -1, nil, fmt.Errorf("must provide a command to execute: %w", define.ErrInvalidArg)
35
	}
36

37
	if sessionID == "" {
38
		return -1, nil, fmt.Errorf("must provide a session ID for exec: %w", define.ErrEmptyID)
39
	}
40

41
	// TODO: Should we default this to false?
42
	// Or maybe make streams mandatory?
43
	attachStdin := true
44
	if streams != nil {
45
		attachStdin = streams.AttachInput
46
	}
47

48
	var ociLog string
49
	if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
50
		ociLog = c.execOCILog(sessionID)
51
	}
52

53
	execCmd, pipes, err := r.startExec(c, sessionID, options, attachStdin, ociLog)
54
	if err != nil {
55
		return -1, nil, err
56
	}
57

58
	// Only close sync pipe. Start and attach are consumed in the attach
59
	// goroutine.
60
	defer func() {
61
		if pipes.syncPipe != nil && !pipes.syncClosed {
62
			errorhandling.CloseQuiet(pipes.syncPipe)
63
			pipes.syncClosed = true
64
		}
65
	}()
66

67
	// TODO Only create if !detach
68
	// Attach to the container before starting it
69
	attachChan := make(chan error)
70
	go func() {
71
		// attachToExec is responsible for closing pipes
72
		attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe, newSize)
73
		close(attachChan)
74
	}()
75

76
	if err := execCmd.Wait(); err != nil {
77
		return -1, nil, fmt.Errorf("cannot run conmon: %w", err)
78
	}
79

80
	pid, err := readConmonPipeData(r.name, pipes.syncPipe, ociLog)
81

82
	return pid, attachChan, err
83
}
84

85
// ExecContainerHTTP executes a new command in an existing container and
86
// forwards its standard streams over an attach
87
func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter,
88
	streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *resize.TerminalSize) (int, chan error, error) {
89
	if streams != nil {
90
		if !streams.Stdin && !streams.Stdout && !streams.Stderr {
91
			return -1, nil, fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg)
92
		}
93
	}
94

95
	if options == nil {
96
		return -1, nil, fmt.Errorf("must provide exec options to ExecContainerHTTP: %w", define.ErrInvalidArg)
97
	}
98

99
	detachString := config.DefaultDetachKeys
100
	if options.DetachKeys != nil {
101
		detachString = *options.DetachKeys
102
	}
103
	detachKeys, err := processDetachKeys(detachString)
104
	if err != nil {
105
		return -1, nil, err
106
	}
107

108
	// TODO: Should we default this to false?
109
	// Or maybe make streams mandatory?
110
	attachStdin := true
111
	if streams != nil {
112
		attachStdin = streams.Stdin
113
	}
114

115
	var ociLog string
116
	if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
117
		ociLog = ctr.execOCILog(sessionID)
118
	}
119

120
	execCmd, pipes, err := r.startExec(ctr, sessionID, options, attachStdin, ociLog)
121
	if err != nil {
122
		return -1, nil, err
123
	}
124

125
	// Only close sync pipe. Start and attach are consumed in the attach
126
	// goroutine.
127
	defer func() {
128
		if pipes.syncPipe != nil && !pipes.syncClosed {
129
			errorhandling.CloseQuiet(pipes.syncPipe)
130
			pipes.syncClosed = true
131
		}
132
	}()
133

134
	attachChan := make(chan error)
135
	conmonPipeDataChan := make(chan conmonPipeData)
136
	go func() {
137
		// attachToExec is responsible for closing pipes
138
		attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog, newSize, r.name)
139
		close(attachChan)
140
	}()
141

142
	// NOTE: the channel is needed to communicate conmon's data.  In case
143
	// of an error, the error will be written on the hijacked http
144
	// connection such that remote clients will receive the error.
145
	pipeData := <-conmonPipeDataChan
146

147
	return pipeData.pid, attachChan, pipeData.err
148
}
149

150
// conmonPipeData contains the data when reading from conmon's pipe.
151
type conmonPipeData struct {
152
	pid int
153
	err error
154
}
155

156
// ExecContainerDetached executes a command in a running container, but does
157
// not attach to it.
158
func (r *ConmonOCIRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) {
159
	if options == nil {
160
		return -1, fmt.Errorf("must provide exec options to ExecContainerHTTP: %w", define.ErrInvalidArg)
161
	}
162

163
	var ociLog string
164
	if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
165
		ociLog = ctr.execOCILog(sessionID)
166
	}
167

168
	execCmd, pipes, err := r.startExec(ctr, sessionID, options, stdin, ociLog)
169
	if err != nil {
170
		return -1, err
171
	}
172

173
	defer func() {
174
		pipes.cleanup()
175
	}()
176

177
	// Wait for Conmon to tell us we're ready to attach.
178
	// We aren't actually *going* to attach, but this means that we're good
179
	// to proceed.
180
	if _, err := readConmonPipeData(r.name, pipes.attachPipe, ""); err != nil {
181
		return -1, err
182
	}
183

184
	// Start the exec session
185
	if err := writeConmonPipeData(pipes.startPipe); err != nil {
186
		return -1, err
187
	}
188

189
	// Wait for conmon to succeed, when return.
190
	if err := execCmd.Wait(); err != nil {
191
		return -1, fmt.Errorf("cannot run conmon: %w", err)
192
	}
193

194
	pid, err := readConmonPipeData(r.name, pipes.syncPipe, ociLog)
195

196
	return pid, err
197
}
198

199
// ExecAttachResize resizes the TTY of the given exec session.
200
func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize resize.TerminalSize) error {
201
	controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID))
202
	if err != nil {
203
		return err
204
	}
205
	defer controlFile.Close()
206

207
	if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil {
208
		return fmt.Errorf("failed to write to ctl file to resize terminal: %w", err)
209
	}
210

211
	return nil
212
}
213

214
// ExecStopContainer stops a given exec session in a running container.
215
func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error {
216
	pid, err := ctr.getExecSessionPID(sessionID)
217
	if err != nil {
218
		return err
219
	}
220

221
	logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID)
222

223
	// Is the session dead?
224
	// Ping the PID with signal 0 to see if it still exists.
225
	if err := unix.Kill(pid, 0); err != nil {
226
		if err == unix.ESRCH {
227
			return nil
228
		}
229
		return fmt.Errorf("pinging container %s exec session %s PID %d with signal 0: %w", ctr.ID(), sessionID, pid, err)
230
	}
231

232
	if timeout > 0 {
233
		// Use SIGTERM by default, then SIGSTOP after timeout.
234
		logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID())
235
		if err := unix.Kill(pid, unix.SIGTERM); err != nil {
236
			if err == unix.ESRCH {
237
				return nil
238
			}
239
			return fmt.Errorf("killing container %s exec session %s PID %d with SIGTERM: %w", ctr.ID(), sessionID, pid, err)
240
		}
241

242
		// Wait for the PID to stop
243
		if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil {
244
			logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err)
245
		} else {
246
			// No error, container is dead
247
			return nil
248
		}
249
	}
250

251
	// SIGTERM did not work. On to SIGKILL.
252
	logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID())
253
	if err := unix.Kill(pid, unix.SIGTERM); err != nil {
254
		if err == unix.ESRCH {
255
			return nil
256
		}
257
		return fmt.Errorf("killing container %s exec session %s PID %d with SIGKILL: %w", ctr.ID(), sessionID, pid, err)
258
	}
259

260
	// Wait for the PID to stop
261
	if err := waitPidStop(pid, killContainerTimeout); err != nil {
262
		return fmt.Errorf("timed out waiting for container %s exec session %s PID %d to stop after SIGKILL: %w", ctr.ID(), sessionID, pid, err)
263
	}
264

265
	return nil
266
}
267

268
// ExecUpdateStatus checks if the given exec session is still running.
269
func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) {
270
	pid, err := ctr.getExecSessionPID(sessionID)
271
	if err != nil {
272
		return false, err
273
	}
274

275
	logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID)
276

277
	// Is the session dead?
278
	// Ping the PID with signal 0 to see if it still exists.
279
	if err := unix.Kill(pid, 0); err != nil {
280
		if err == unix.ESRCH {
281
			return false, nil
282
		}
283
		return false, fmt.Errorf("pinging container %s exec session %s PID %d with signal 0: %w", ctr.ID(), sessionID, pid, err)
284
	}
285

286
	return true, nil
287
}
288

289
// ExecAttachSocketPath is the path to a container's exec session attach socket.
290
func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) {
291
	// We don't even use container, so don't validity check it
292
	if sessionID == "" {
293
		return "", fmt.Errorf("must provide a valid session ID to get attach socket path: %w", define.ErrInvalidArg)
294
	}
295

296
	return filepath.Join(ctr.execBundlePath(sessionID), "attach"), nil
297
}
298

299
// This contains pipes used by the exec API.
300
type execPipes struct {
301
	syncPipe     *os.File
302
	syncClosed   bool
303
	startPipe    *os.File
304
	startClosed  bool
305
	attachPipe   *os.File
306
	attachClosed bool
307
}
308

309
func (p *execPipes) cleanup() {
310
	if p.syncPipe != nil && !p.syncClosed {
311
		errorhandling.CloseQuiet(p.syncPipe)
312
		p.syncClosed = true
313
	}
314
	if p.startPipe != nil && !p.startClosed {
315
		errorhandling.CloseQuiet(p.startPipe)
316
		p.startClosed = true
317
	}
318
	if p.attachPipe != nil && !p.attachClosed {
319
		errorhandling.CloseQuiet(p.attachPipe)
320
		p.attachClosed = true
321
	}
322
}
323

324
// Start an exec session's conmon parent from the given options.
325
func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *ExecOptions, attachStdin bool, ociLog string) (_ *exec.Cmd, _ *execPipes, deferredErr error) {
326
	pipes := new(execPipes)
327

328
	if options == nil {
329
		return nil, nil, fmt.Errorf("must provide an ExecOptions struct to ExecContainer: %w", define.ErrInvalidArg)
330
	}
331
	if len(options.Cmd) == 0 {
332
		return nil, nil, fmt.Errorf("must provide a command to execute: %w", define.ErrInvalidArg)
333
	}
334

335
	if sessionID == "" {
336
		return nil, nil, fmt.Errorf("must provide a session ID for exec: %w", define.ErrEmptyID)
337
	}
338

339
	// create sync pipe to receive the pid
340
	parentSyncPipe, childSyncPipe, err := newPipe()
341
	if err != nil {
342
		return nil, nil, fmt.Errorf("creating socket pair: %w", err)
343
	}
344
	pipes.syncPipe = parentSyncPipe
345

346
	defer func() {
347
		if deferredErr != nil {
348
			pipes.cleanup()
349
		}
350
	}()
351

352
	// create start pipe to set the cgroup before running
353
	// attachToExec is responsible for closing parentStartPipe
354
	childStartPipe, parentStartPipe, err := newPipe()
355
	if err != nil {
356
		return nil, nil, fmt.Errorf("creating socket pair: %w", err)
357
	}
358
	pipes.startPipe = parentStartPipe
359

360
	// create the attach pipe to allow attach socket to be created before
361
	// $RUNTIME exec starts running. This is to make sure we can capture all output
362
	// from the process through that socket, rather than half reading the log, half attaching to the socket
363
	// attachToExec is responsible for closing parentAttachPipe
364
	parentAttachPipe, childAttachPipe, err := newPipe()
365
	if err != nil {
366
		return nil, nil, fmt.Errorf("creating socket pair: %w", err)
367
	}
368
	pipes.attachPipe = parentAttachPipe
369

370
	childrenClosed := false
371
	defer func() {
372
		if !childrenClosed {
373
			errorhandling.CloseQuiet(childSyncPipe)
374
			errorhandling.CloseQuiet(childAttachPipe)
375
			errorhandling.CloseQuiet(childStartPipe)
376
		}
377
	}()
378

379
	finalEnv := make([]string, 0, len(options.Env))
380
	for k, v := range options.Env {
381
		finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v))
382
	}
383

384
	processFile, err := c.prepareProcessExec(options, finalEnv, sessionID)
385
	if err != nil {
386
		return nil, nil, err
387
	}
388
	defer processFile.Close()
389

390
	args, err := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), c.execPersistDir(sessionID), ociLog, define.NoLogging, c.config.LogTag)
391
	if err != nil {
392
		return nil, nil, err
393
	}
394

395
	preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(options.PreserveFD, options.PreserveFDs)
396
	if err != nil {
397
		return nil, nil, err
398
	}
399

400
	if preserveFDs > 0 {
401
		args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
402
	}
403

404
	if options.Terminal {
405
		args = append(args, "-t")
406
	}
407

408
	if attachStdin {
409
		args = append(args, "-i")
410
	}
411

412
	// Append container ID and command
413
	args = append(args, "-e")
414
	// TODO make this optional when we can detach
415
	args = append(args, "--exec-attach")
416
	args = append(args, "--exec-process-spec", processFile.Name())
417

418
	if len(options.ExitCommand) > 0 {
419
		args = append(args, "--exit-command", options.ExitCommand[0])
420
		for _, arg := range options.ExitCommand[1:] {
421
			args = append(args, []string{"--exit-command-arg", arg}...)
422
		}
423
		if options.ExitCommandDelay > 0 {
424
			args = append(args, []string{"--exit-delay", strconv.FormatUint(uint64(options.ExitCommandDelay), 10)}...)
425
		}
426
	}
427

428
	logrus.WithFields(logrus.Fields{
429
		"args": args,
430
	}).Debugf("running conmon: %s", r.conmonPath)
431
	execCmd := exec.Command(r.conmonPath, args...)
432

433
	// TODO: This is commented because it doesn't make much sense in HTTP
434
	// attach, and I'm not certain it does for non-HTTP attach as well.
435
	// if streams != nil {
436
	// 	// Don't add the InputStream to the execCmd. Instead, the data should be passed
437
	// 	// through CopyDetachable
438
	// 	if streams.AttachOutput {
439
	// 		execCmd.Stdout = options.Streams.OutputStream
440
	// 	}
441
	// 	if streams.AttachError {
442
	// 		execCmd.Stderr = options.Streams.ErrorStream
443
	// 	}
444
	// }
445

446
	conmonEnv, err := r.configureConmonEnv()
447
	if err != nil {
448
		return nil, nil, fmt.Errorf("configuring conmon env: %w", err)
449
	}
450

451
	execCmd.ExtraFiles = extraFiles
452

453
	// we don't want to step on users fds they asked to preserve
454
	// Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3
455
	execCmd.Env = r.conmonEnv
456
	execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5))
457
	execCmd.Env = append(execCmd.Env, conmonEnv...)
458

459
	execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe)
460
	execCmd.Dir = c.execBundlePath(sessionID)
461
	execCmd.SysProcAttr = &syscall.SysProcAttr{
462
		Setpgid: true,
463
	}
464

465
	err = execCmd.Start()
466

467
	// We don't need children pipes  on the parent side
468
	errorhandling.CloseQuiet(childSyncPipe)
469
	errorhandling.CloseQuiet(childAttachPipe)
470
	errorhandling.CloseQuiet(childStartPipe)
471
	childrenClosed = true
472

473
	if err != nil {
474
		return nil, nil, fmt.Errorf("cannot start container %s: %w", c.ID(), err)
475
	}
476
	if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil {
477
		return nil, nil, err
478
	}
479

480
	// These fds were passed down to the runtime.  Close them
481
	// and not interfere
482
	for _, f := range filesToClose {
483
		errorhandling.CloseQuiet(f)
484
	}
485

486
	return execCmd, pipes, nil
487
}
488

489
// Attach to a container over HTTP
490
func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, execCmd *exec.Cmd, conmonPipeDataChan chan<- conmonPipeData, ociLog string, newSize *resize.TerminalSize, runtimeName string) (deferredErr error) {
491
	// NOTE: As you may notice, the attach code is quite complex.
492
	// Many things happen concurrently and yet are interdependent.
493
	// If you ever change this function, make sure to write to the
494
	// conmonPipeDataChan in case of an error.
495

496
	if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil {
497
		err := fmt.Errorf("must provide a start and attach pipe to finish an exec attach: %w", define.ErrInvalidArg)
498
		conmonPipeDataChan <- conmonPipeData{-1, err}
499
		return err
500
	}
501

502
	defer func() {
503
		if !pipes.startClosed {
504
			errorhandling.CloseQuiet(pipes.startPipe)
505
			pipes.startClosed = true
506
		}
507
		if !pipes.attachClosed {
508
			errorhandling.CloseQuiet(pipes.attachPipe)
509
			pipes.attachClosed = true
510
		}
511
	}()
512

513
	logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID)
514

515
	// set up the socket path, such that it is the correct length and location for exec
516
	sockPath, err := c.execAttachSocketPath(sessionID)
517
	if err != nil {
518
		conmonPipeDataChan <- conmonPipeData{-1, err}
519
		return err
520
	}
521

522
	// 2: read from attachFd that the parent process has set up the console socket
523
	if _, err := readConmonPipeData(runtimeName, pipes.attachPipe, ""); err != nil {
524
		conmonPipeDataChan <- conmonPipeData{-1, err}
525
		return err
526
	}
527

528
	// resize before we start the container process
529
	if newSize != nil {
530
		err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize)
531
		if err != nil {
532
			logrus.Warnf("Resize failed: %v", err)
533
		}
534
	}
535

536
	// 2: then attach
537
	conn, err := openUnixSocket(sockPath)
538
	if err != nil {
539
		conmonPipeDataChan <- conmonPipeData{-1, err}
540
		return fmt.Errorf("failed to connect to container's attach socket: %v: %w", sockPath, err)
541
	}
542
	defer func() {
543
		if err := conn.Close(); err != nil {
544
			logrus.Errorf("Unable to close socket: %q", err)
545
		}
546
	}()
547

548
	attachStdout := true
549
	attachStderr := true
550
	attachStdin := true
551
	if streams != nil {
552
		attachStdout = streams.Stdout
553
		attachStderr = streams.Stderr
554
		attachStdin = streams.Stdin
555
	}
556

557
	// Perform hijack
558
	hijacker, ok := w.(http.Hijacker)
559
	if !ok {
560
		conmonPipeDataChan <- conmonPipeData{-1, err}
561
		return errors.New("unable to hijack connection")
562
	}
563

564
	httpCon, httpBuf, err := hijacker.Hijack()
565
	if err != nil {
566
		conmonPipeDataChan <- conmonPipeData{-1, err}
567
		return fmt.Errorf("hijacking connection: %w", err)
568
	}
569

570
	hijackDone <- true
571

572
	// Write a header to let the client know what happened
573
	writeHijackHeader(r, httpBuf, isTerminal)
574

575
	// Force a flush after the header is written.
576
	if err := httpBuf.Flush(); err != nil {
577
		conmonPipeDataChan <- conmonPipeData{-1, err}
578
		return fmt.Errorf("flushing HTTP hijack header: %w", err)
579
	}
580

581
	go func() {
582
		// Wait for conmon to succeed, when return.
583
		if err := execCmd.Wait(); err != nil {
584
			conmonPipeDataChan <- conmonPipeData{-1, err}
585
		} else {
586
			pid, err := readConmonPipeData(runtimeName, pipes.syncPipe, ociLog)
587
			if err != nil {
588
				hijackWriteError(err, c.ID(), isTerminal, httpBuf)
589
				conmonPipeDataChan <- conmonPipeData{pid, err}
590
			} else {
591
				conmonPipeDataChan <- conmonPipeData{pid, err}
592
			}
593
		}
594
		// We need to hold the connection open until the complete exec
595
		// function has finished. This channel will be closed in a defer
596
		// in that function, so we can wait for it here.
597
		// Can't be a defer, because this would block the function from
598
		// returning.
599
		<-holdConnOpen
600
		hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf)
601
	}()
602

603
	stdoutChan := make(chan error)
604
	stdinChan := make(chan error)
605

606
	// Next, STDIN. Avoid entirely if attachStdin unset.
607
	if attachStdin {
608
		go func() {
609
			logrus.Debugf("Beginning STDIN copy")
610
			_, err := detach.Copy(conn, httpBuf, detachKeys)
611
			logrus.Debugf("STDIN copy completed")
612
			stdinChan <- err
613
		}()
614
	}
615

616
	// 4: send start message to child
617
	if err := writeConmonPipeData(pipes.startPipe); err != nil {
618
		return err
619
	}
620

621
	// Handle STDOUT/STDERR *after* start message is sent
622
	go func() {
623
		var err error
624
		if isTerminal {
625
			// Hack: return immediately if attachStdout not set to
626
			// emulate Docker.
627
			// Basically, when terminal is set, STDERR goes nowhere.
628
			// Everything does over STDOUT.
629
			// Therefore, if not attaching STDOUT - we'll never copy
630
			// anything from here.
631
			logrus.Debugf("Performing terminal HTTP attach for container %s", c.ID())
632
			if attachStdout {
633
				err = httpAttachTerminalCopy(conn, httpBuf, c.ID())
634
			}
635
		} else {
636
			logrus.Debugf("Performing non-terminal HTTP attach for container %s", c.ID())
637
			err = httpAttachNonTerminalCopy(conn, httpBuf, c.ID(), attachStdin, attachStdout, attachStderr)
638
		}
639
		stdoutChan <- err
640
		logrus.Debugf("STDOUT/ERR copy completed")
641
	}()
642

643
	for {
644
		select {
645
		case err := <-stdoutChan:
646
			if err != nil {
647
				return err
648
			}
649

650
			return nil
651
		case err := <-stdinChan:
652
			if err != nil {
653
				return err
654
			}
655
			// copy stdin is done, close it
656
			if connErr := socketCloseWrite(conn); connErr != nil {
657
				logrus.Errorf("Unable to close conn: %v", connErr)
658
			}
659
		case <-cancel:
660
			return nil
661
		}
662
	}
663
}
664

665
// prepareProcessExec returns the path of the process.json used in runc exec -p
666
// caller is responsible to close the returned *os.File if needed.
667
func (c *Container) prepareProcessExec(options *ExecOptions, env []string, sessionID string) (*os.File, error) {
668
	f, err := os.CreateTemp(c.execBundlePath(sessionID), "exec-process-")
669
	if err != nil {
670
		return nil, err
671
	}
672
	pspec := new(spec.Process)
673
	if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil {
674
		return nil, err
675
	}
676
	pspec.SelinuxLabel = c.config.ProcessLabel
677
	pspec.Args = options.Cmd
678

679
	// We need to default this to false else it will inherit terminal as true
680
	// from the container.
681
	pspec.Terminal = false
682
	if options.Terminal {
683
		pspec.Terminal = true
684
	}
685
	if len(env) > 0 {
686
		pspec.Env = append(pspec.Env, env...)
687
	}
688

689
	// Add secret envs if they exist
690
	manager, err := c.runtime.SecretsManager()
691
	if err != nil {
692
		return nil, err
693
	}
694
	for name, secr := range c.config.EnvSecrets {
695
		_, data, err := manager.LookupSecretData(secr.Name)
696
		if err != nil {
697
			return nil, err
698
		}
699
		pspec.Env = append(pspec.Env, fmt.Sprintf("%s=%s", name, string(data)))
700
	}
701

702
	if options.Cwd != "" {
703
		pspec.Cwd = options.Cwd
704
	}
705

706
	var addGroups []string
707
	var sgids []uint32
708

709
	// if the user is empty, we should inherit the user that the container is currently running with
710
	user := options.User
711
	if user == "" {
712
		logrus.Debugf("Set user to %s", c.config.User)
713
		user = c.config.User
714
		addGroups = c.config.Groups
715
	}
716

717
	overrides := c.getUserOverrides()
718
	execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides)
719
	if err != nil {
720
		return nil, err
721
	}
722

723
	if len(addGroups) > 0 {
724
		sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides)
725
		if err != nil {
726
			return nil, fmt.Errorf("looking up supplemental groups for container %s exec session %s: %w", c.ID(), sessionID, err)
727
		}
728
	}
729

730
	// If user was set, look it up in the container to get a UID to use on
731
	// the host
732
	if user != "" || len(sgids) > 0 {
733
		if user != "" {
734
			for _, sgid := range execUser.Sgids {
735
				sgids = append(sgids, uint32(sgid))
736
			}
737
		}
738
		processUser := spec.User{
739
			UID:            uint32(execUser.Uid),
740
			GID:            uint32(execUser.Gid),
741
			AdditionalGids: sgids,
742
		}
743

744
		pspec.User = processUser
745
	}
746

747
	if c.config.Umask != "" {
748
		umask, err := c.umask()
749
		if err != nil {
750
			return nil, err
751
		}
752
		pspec.User.Umask = &umask
753
	}
754

755
	if err := c.setProcessCapabilitiesExec(options, user, execUser, pspec); err != nil {
756
		return nil, err
757
	}
758

759
	hasHomeSet := false
760
	for _, s := range pspec.Env {
761
		if strings.HasPrefix(s, "HOME=") {
762
			hasHomeSet = true
763
			break
764
		}
765
	}
766
	if !hasHomeSet {
767
		pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home))
768
	}
769

770
	processJSON, err := json.Marshal(pspec)
771
	if err != nil {
772
		return nil, err
773
	}
774

775
	if err := os.WriteFile(f.Name(), processJSON, 0644); err != nil {
776
		return nil, err
777
	}
778
	return f, nil
779
}
780

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

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

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

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