github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/oci_conmon_exec_linux.go (about) 1 package libpod 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/containers/common/pkg/capabilities" 15 "github.com/containers/common/pkg/config" 16 "github.com/containers/podman/v3/libpod/define" 17 "github.com/containers/podman/v3/pkg/errorhandling" 18 "github.com/containers/podman/v3/pkg/lookup" 19 "github.com/containers/podman/v3/pkg/util" 20 "github.com/containers/podman/v3/utils" 21 spec "github.com/opencontainers/runtime-spec/specs-go" 22 "github.com/pkg/errors" 23 "github.com/sirupsen/logrus" 24 "golang.org/x/sys/unix" 25 ) 26 27 // ExecContainer executes a command in a running container 28 func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *define.TerminalSize) (int, chan error, error) { 29 if options == nil { 30 return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") 31 } 32 if len(options.Cmd) == 0 { 33 return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") 34 } 35 36 if sessionID == "" { 37 return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") 38 } 39 40 // TODO: Should we default this to false? 41 // Or maybe make streams mandatory? 42 attachStdin := true 43 if streams != nil { 44 attachStdin = streams.AttachInput 45 } 46 47 var ociLog string 48 if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { 49 ociLog = c.execOCILog(sessionID) 50 } 51 52 execCmd, pipes, err := r.startExec(c, sessionID, options, attachStdin, ociLog) 53 if err != nil { 54 return -1, nil, err 55 } 56 57 // Only close sync pipe. Start and attach are consumed in the attach 58 // goroutine. 59 defer func() { 60 if pipes.syncPipe != nil && !pipes.syncClosed { 61 errorhandling.CloseQuiet(pipes.syncPipe) 62 pipes.syncClosed = true 63 } 64 }() 65 66 // TODO Only create if !detach 67 // Attach to the container before starting it 68 attachChan := make(chan error) 69 go func() { 70 // attachToExec is responsible for closing pipes 71 attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe, newSize) 72 close(attachChan) 73 }() 74 75 if err := execCmd.Wait(); err != nil { 76 return -1, nil, errors.Wrapf(err, "cannot run conmon") 77 } 78 79 pid, err := readConmonPipeData(pipes.syncPipe, ociLog) 80 81 return pid, attachChan, err 82 } 83 84 // ExecContainerHTTP executes a new command in an existing container and 85 // forwards its standard streams over an attach 86 func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, 87 streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *define.TerminalSize) (int, chan error, error) { 88 if streams != nil { 89 if !streams.Stdin && !streams.Stdout && !streams.Stderr { 90 return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") 91 } 92 } 93 94 if options == nil { 95 return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") 96 } 97 98 detachString := config.DefaultDetachKeys 99 if options.DetachKeys != nil { 100 detachString = *options.DetachKeys 101 } 102 detachKeys, err := processDetachKeys(detachString) 103 if err != nil { 104 return -1, nil, err 105 } 106 107 // TODO: Should we default this to false? 108 // Or maybe make streams mandatory? 109 attachStdin := true 110 if streams != nil { 111 attachStdin = streams.Stdin 112 } 113 114 var ociLog string 115 if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { 116 ociLog = ctr.execOCILog(sessionID) 117 } 118 119 execCmd, pipes, err := r.startExec(ctr, sessionID, options, attachStdin, ociLog) 120 if err != nil { 121 return -1, nil, err 122 } 123 124 // Only close sync pipe. Start and attach are consumed in the attach 125 // goroutine. 126 defer func() { 127 if pipes.syncPipe != nil && !pipes.syncClosed { 128 errorhandling.CloseQuiet(pipes.syncPipe) 129 pipes.syncClosed = true 130 } 131 }() 132 133 attachChan := make(chan error) 134 conmonPipeDataChan := make(chan conmonPipeData) 135 go func() { 136 // attachToExec is responsible for closing pipes 137 attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog, newSize) 138 close(attachChan) 139 }() 140 141 // NOTE: the channel is needed to communicate conmon's data. In case 142 // of an error, the error will be written on the hijacked http 143 // connection such that remote clients will receive the error. 144 pipeData := <-conmonPipeDataChan 145 146 return pipeData.pid, attachChan, pipeData.err 147 } 148 149 // conmonPipeData contains the data when reading from conmon's pipe. 150 type conmonPipeData struct { 151 pid int 152 err error 153 } 154 155 // ExecContainerDetached executes a command in a running container, but does 156 // not attach to it. 157 func (r *ConmonOCIRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { 158 if options == nil { 159 return -1, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") 160 } 161 162 var ociLog string 163 if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { 164 ociLog = ctr.execOCILog(sessionID) 165 } 166 167 execCmd, pipes, err := r.startExec(ctr, sessionID, options, stdin, ociLog) 168 if err != nil { 169 return -1, err 170 } 171 172 defer func() { 173 pipes.cleanup() 174 }() 175 176 // Wait for Conmon to tell us we're ready to attach. 177 // We aren't actually *going* to attach, but this means that we're good 178 // to proceed. 179 if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { 180 return -1, err 181 } 182 183 // Start the exec session 184 if err := writeConmonPipeData(pipes.startPipe); err != nil { 185 return -1, err 186 } 187 188 // Wait for conmon to succeed, when return. 189 if err := execCmd.Wait(); err != nil { 190 return -1, errors.Wrapf(err, "cannot run conmon") 191 } 192 193 pid, err := readConmonPipeData(pipes.syncPipe, ociLog) 194 195 return pid, err 196 } 197 198 // ExecAttachResize resizes the TTY of the given exec session. 199 func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize define.TerminalSize) error { 200 controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID)) 201 if err != nil { 202 return err 203 } 204 defer controlFile.Close() 205 206 if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil { 207 return errors.Wrapf(err, "failed to write to ctl file to resize terminal") 208 } 209 210 return nil 211 } 212 213 // ExecStopContainer stops a given exec session in a running container. 214 func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { 215 pid, err := ctr.getExecSessionPID(sessionID) 216 if err != nil { 217 return err 218 } 219 220 logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID) 221 222 // Is the session dead? 223 // Ping the PID with signal 0 to see if it still exists. 224 if err := unix.Kill(pid, 0); err != nil { 225 if err == unix.ESRCH { 226 return nil 227 } 228 return errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) 229 } 230 231 if timeout > 0 { 232 // Use SIGTERM by default, then SIGSTOP after timeout. 233 logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID()) 234 if err := unix.Kill(pid, unix.SIGTERM); err != nil { 235 if err == unix.ESRCH { 236 return nil 237 } 238 return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGTERM", ctr.ID(), sessionID, pid) 239 } 240 241 // Wait for the PID to stop 242 if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil { 243 logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err) 244 } else { 245 // No error, container is dead 246 return nil 247 } 248 } 249 250 // SIGTERM did not work. On to SIGKILL. 251 logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID()) 252 if err := unix.Kill(pid, unix.SIGTERM); err != nil { 253 if err == unix.ESRCH { 254 return nil 255 } 256 return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGKILL", ctr.ID(), sessionID, pid) 257 } 258 259 // Wait for the PID to stop 260 if err := waitPidStop(pid, killContainerTimeout*time.Second); err != nil { 261 return errors.Wrapf(err, "timed out waiting for container %s exec session %s PID %d to stop after SIGKILL", ctr.ID(), sessionID, pid) 262 } 263 264 return nil 265 } 266 267 // ExecUpdateStatus checks if the given exec session is still running. 268 func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { 269 pid, err := ctr.getExecSessionPID(sessionID) 270 if err != nil { 271 return false, err 272 } 273 274 logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) 275 276 // Is the session dead? 277 // Ping the PID with signal 0 to see if it still exists. 278 if err := unix.Kill(pid, 0); err != nil { 279 if err == unix.ESRCH { 280 return false, nil 281 } 282 return false, errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) 283 } 284 285 return true, nil 286 } 287 288 // ExecAttachSocketPath is the path to a container's exec session attach socket. 289 func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { 290 // We don't even use container, so don't validity check it 291 if sessionID == "" { 292 return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid session ID to get attach socket path") 293 } 294 295 return filepath.Join(ctr.execBundlePath(sessionID), "attach"), nil 296 } 297 298 // This contains pipes used by the exec API. 299 type execPipes struct { 300 syncPipe *os.File 301 syncClosed bool 302 startPipe *os.File 303 startClosed bool 304 attachPipe *os.File 305 attachClosed bool 306 } 307 308 func (p *execPipes) cleanup() { 309 if p.syncPipe != nil && !p.syncClosed { 310 errorhandling.CloseQuiet(p.syncPipe) 311 p.syncClosed = true 312 } 313 if p.startPipe != nil && !p.startClosed { 314 errorhandling.CloseQuiet(p.startPipe) 315 p.startClosed = true 316 } 317 if p.attachPipe != nil && !p.attachClosed { 318 errorhandling.CloseQuiet(p.attachPipe) 319 p.attachClosed = true 320 } 321 } 322 323 // Start an exec session's conmon parent from the given options. 324 func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *ExecOptions, attachStdin bool, ociLog string) (_ *exec.Cmd, _ *execPipes, deferredErr error) { 325 pipes := new(execPipes) 326 327 if options == nil { 328 return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") 329 } 330 if len(options.Cmd) == 0 { 331 return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") 332 } 333 334 if sessionID == "" { 335 return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") 336 } 337 338 // create sync pipe to receive the pid 339 parentSyncPipe, childSyncPipe, err := newPipe() 340 if err != nil { 341 return nil, nil, errors.Wrapf(err, "error creating socket pair") 342 } 343 pipes.syncPipe = parentSyncPipe 344 345 defer func() { 346 if deferredErr != nil { 347 pipes.cleanup() 348 } 349 }() 350 351 // create start pipe to set the cgroup before running 352 // attachToExec is responsible for closing parentStartPipe 353 childStartPipe, parentStartPipe, err := newPipe() 354 if err != nil { 355 return nil, nil, errors.Wrapf(err, "error creating socket pair") 356 } 357 pipes.startPipe = parentStartPipe 358 359 // create the attach pipe to allow attach socket to be created before 360 // $RUNTIME exec starts running. This is to make sure we can capture all output 361 // from the process through that socket, rather than half reading the log, half attaching to the socket 362 // attachToExec is responsible for closing parentAttachPipe 363 parentAttachPipe, childAttachPipe, err := newPipe() 364 if err != nil { 365 return nil, nil, errors.Wrapf(err, "error creating socket pair") 366 } 367 pipes.attachPipe = parentAttachPipe 368 369 childrenClosed := false 370 defer func() { 371 if !childrenClosed { 372 errorhandling.CloseQuiet(childSyncPipe) 373 errorhandling.CloseQuiet(childAttachPipe) 374 errorhandling.CloseQuiet(childStartPipe) 375 } 376 }() 377 378 runtimeDir, err := util.GetRuntimeDir() 379 if err != nil { 380 return nil, nil, err 381 } 382 383 finalEnv := make([]string, 0, len(options.Env)) 384 for k, v := range options.Env { 385 finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) 386 } 387 388 processFile, err := prepareProcessExec(c, options, finalEnv, sessionID) 389 if err != nil { 390 return nil, nil, err 391 } 392 393 args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, define.NoLogging, "") 394 395 if options.PreserveFDs > 0 { 396 args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...) 397 } 398 399 if options.Terminal { 400 args = append(args, "-t") 401 } 402 403 if attachStdin { 404 args = append(args, "-i") 405 } 406 407 // Append container ID and command 408 args = append(args, "-e") 409 // TODO make this optional when we can detach 410 args = append(args, "--exec-attach") 411 args = append(args, "--exec-process-spec", processFile.Name()) 412 413 if len(options.ExitCommand) > 0 { 414 args = append(args, "--exit-command", options.ExitCommand[0]) 415 for _, arg := range options.ExitCommand[1:] { 416 args = append(args, []string{"--exit-command-arg", arg}...) 417 } 418 if options.ExitCommandDelay > 0 { 419 args = append(args, []string{"--exit-delay", fmt.Sprintf("%d", options.ExitCommandDelay)}...) 420 } 421 } 422 423 logrus.WithFields(logrus.Fields{ 424 "args": args, 425 }).Debugf("running conmon: %s", r.conmonPath) 426 execCmd := exec.Command(r.conmonPath, args...) 427 428 // TODO: This is commented because it doesn't make much sense in HTTP 429 // attach, and I'm not certain it does for non-HTTP attach as well. 430 // if streams != nil { 431 // // Don't add the InputStream to the execCmd. Instead, the data should be passed 432 // // through CopyDetachable 433 // if streams.AttachOutput { 434 // execCmd.Stdout = options.Streams.OutputStream 435 // } 436 // if streams.AttachError { 437 // execCmd.Stderr = options.Streams.ErrorStream 438 // } 439 // } 440 441 conmonEnv := r.configureConmonEnv(c, runtimeDir) 442 443 var filesToClose []*os.File 444 if options.PreserveFDs > 0 { 445 for fd := 3; fd < int(3+options.PreserveFDs); fd++ { 446 f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)) 447 filesToClose = append(filesToClose, f) 448 execCmd.ExtraFiles = append(execCmd.ExtraFiles, f) 449 } 450 } 451 452 // we don't want to step on users fds they asked to preserve 453 // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 454 execCmd.Env = r.conmonEnv 455 execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5)) 456 execCmd.Env = append(execCmd.Env, conmonEnv...) 457 458 execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) 459 execCmd.Dir = c.execBundlePath(sessionID) 460 execCmd.SysProcAttr = &syscall.SysProcAttr{ 461 Setpgid: true, 462 } 463 464 err = startCommandGivenSelinux(execCmd, c) 465 466 // We don't need children pipes on the parent side 467 errorhandling.CloseQuiet(childSyncPipe) 468 errorhandling.CloseQuiet(childAttachPipe) 469 errorhandling.CloseQuiet(childStartPipe) 470 childrenClosed = true 471 472 if err != nil { 473 return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) 474 } 475 if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { 476 return nil, nil, err 477 } 478 479 // These fds were passed down to the runtime. Close them 480 // and not interfere 481 for _, f := range filesToClose { 482 errorhandling.CloseQuiet(f) 483 } 484 485 return execCmd, pipes, nil 486 } 487 488 // Attach to a container over HTTP 489 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 *define.TerminalSize) (deferredErr error) { 490 // NOTE: As you may notice, the attach code is quite complex. 491 // Many things happen concurrently and yet are interdependent. 492 // If you ever change this function, make sure to write to the 493 // conmonPipeDataChan in case of an error. 494 495 if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil { 496 err := errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach") 497 conmonPipeDataChan <- conmonPipeData{-1, err} 498 return err 499 } 500 501 defer func() { 502 if !pipes.startClosed { 503 errorhandling.CloseQuiet(pipes.startPipe) 504 pipes.startClosed = true 505 } 506 if !pipes.attachClosed { 507 errorhandling.CloseQuiet(pipes.attachPipe) 508 pipes.attachClosed = true 509 } 510 }() 511 512 logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) 513 514 // set up the socket path, such that it is the correct length and location for exec 515 sockPath, err := c.execAttachSocketPath(sessionID) 516 if err != nil { 517 conmonPipeDataChan <- conmonPipeData{-1, err} 518 return err 519 } 520 521 // 2: read from attachFd that the parent process has set up the console socket 522 if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { 523 conmonPipeDataChan <- conmonPipeData{-1, err} 524 return err 525 } 526 527 // resize before we start the container process 528 if newSize != nil { 529 err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize) 530 if err != nil { 531 logrus.Warn("resize failed", err) 532 } 533 } 534 535 // 2: then attach 536 conn, err := openUnixSocket(sockPath) 537 if err != nil { 538 conmonPipeDataChan <- conmonPipeData{-1, err} 539 return errors.Wrapf(err, "failed to connect to container's attach socket: %v", sockPath) 540 } 541 defer func() { 542 if err := conn.Close(); err != nil { 543 logrus.Errorf("unable to close socket: %q", err) 544 } 545 }() 546 547 attachStdout := true 548 attachStderr := true 549 attachStdin := true 550 if streams != nil { 551 attachStdout = streams.Stdout 552 attachStderr = streams.Stderr 553 attachStdin = streams.Stdin 554 } 555 556 // Perform hijack 557 hijacker, ok := w.(http.Hijacker) 558 if !ok { 559 conmonPipeDataChan <- conmonPipeData{-1, err} 560 return errors.Errorf("unable to hijack connection") 561 } 562 563 httpCon, httpBuf, err := hijacker.Hijack() 564 if err != nil { 565 conmonPipeDataChan <- conmonPipeData{-1, err} 566 return errors.Wrapf(err, "error hijacking connection") 567 } 568 569 hijackDone <- true 570 571 // Write a header to let the client know what happened 572 writeHijackHeader(r, httpBuf) 573 574 // Force a flush after the header is written. 575 if err := httpBuf.Flush(); err != nil { 576 conmonPipeDataChan <- conmonPipeData{-1, err} 577 return errors.Wrapf(err, "error flushing HTTP hijack header") 578 } 579 580 go func() { 581 // Wait for conmon to succeed, when return. 582 if err := execCmd.Wait(); err != nil { 583 conmonPipeDataChan <- conmonPipeData{-1, err} 584 } else { 585 pid, err := readConmonPipeData(pipes.syncPipe, ociLog) 586 if err != nil { 587 hijackWriteError(err, c.ID(), isTerminal, httpBuf) 588 conmonPipeDataChan <- conmonPipeData{pid, err} 589 } else { 590 conmonPipeDataChan <- conmonPipeData{pid, err} 591 } 592 } 593 // We need to hold the connection open until the complete exec 594 // function has finished. This channel will be closed in a defer 595 // in that function, so we can wait for it here. 596 // Can't be a defer, because this would block the function from 597 // returning. 598 <-holdConnOpen 599 hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf) 600 }() 601 602 stdoutChan := make(chan error) 603 stdinChan := make(chan error) 604 605 // Next, STDIN. Avoid entirely if attachStdin unset. 606 if attachStdin { 607 go func() { 608 logrus.Debugf("Beginning STDIN copy") 609 _, err := utils.CopyDetachable(conn, httpBuf, detachKeys) 610 logrus.Debugf("STDIN copy completed") 611 stdinChan <- err 612 }() 613 } 614 615 // 4: send start message to child 616 if err := writeConmonPipeData(pipes.startPipe); err != nil { 617 return err 618 } 619 620 // Handle STDOUT/STDERR *after* start message is sent 621 go func() { 622 var err error 623 if isTerminal { 624 // Hack: return immediately if attachStdout not set to 625 // emulate Docker. 626 // Basically, when terminal is set, STDERR goes nowhere. 627 // Everything does over STDOUT. 628 // Therefore, if not attaching STDOUT - we'll never copy 629 // anything from here. 630 logrus.Debugf("Performing terminal HTTP attach for container %s", c.ID()) 631 if attachStdout { 632 err = httpAttachTerminalCopy(conn, httpBuf, c.ID()) 633 } 634 } else { 635 logrus.Debugf("Performing non-terminal HTTP attach for container %s", c.ID()) 636 err = httpAttachNonTerminalCopy(conn, httpBuf, c.ID(), attachStdin, attachStdout, attachStderr) 637 } 638 stdoutChan <- err 639 logrus.Debugf("STDOUT/ERR copy completed") 640 }() 641 642 for { 643 select { 644 case err := <-stdoutChan: 645 if err != nil { 646 return err 647 } 648 649 return nil 650 case err := <-stdinChan: 651 if err != nil { 652 return err 653 } 654 // copy stdin is done, close it 655 if connErr := conn.CloseWrite(); connErr != nil { 656 logrus.Errorf("Unable to close conn: %v", connErr) 657 } 658 case <-cancel: 659 return nil 660 } 661 } 662 } 663 664 // prepareProcessExec returns the path of the process.json used in runc exec -p 665 // caller is responsible to close the returned *os.File if needed. 666 func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) { 667 f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") 668 if err != nil { 669 return nil, err 670 } 671 pspec := new(spec.Process) 672 if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil { 673 return nil, err 674 } 675 pspec.SelinuxLabel = c.config.ProcessLabel 676 pspec.Args = options.Cmd 677 678 // We need to default this to false else it will inherit terminal as true 679 // from the container. 680 pspec.Terminal = false 681 if options.Terminal { 682 pspec.Terminal = true 683 } 684 if len(env) > 0 { 685 pspec.Env = append(pspec.Env, env...) 686 } 687 688 // Add secret envs if they exist 689 manager, err := c.runtime.SecretsManager() 690 if err != nil { 691 return nil, err 692 } 693 for name, secr := range c.config.EnvSecrets { 694 _, data, err := manager.LookupSecretData(secr.Name) 695 if err != nil { 696 return nil, err 697 } 698 pspec.Env = append(pspec.Env, fmt.Sprintf("%s=%s", name, string(data))) 699 } 700 701 if options.Cwd != "" { 702 pspec.Cwd = options.Cwd 703 } 704 705 var addGroups []string 706 var sgids []uint32 707 708 // if the user is empty, we should inherit the user that the container is currently running with 709 user := options.User 710 if user == "" { 711 logrus.Debugf("Set user to %s", c.config.User) 712 user = c.config.User 713 addGroups = c.config.Groups 714 } 715 716 overrides := c.getUserOverrides() 717 execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) 718 if err != nil { 719 return nil, err 720 } 721 722 if len(addGroups) > 0 { 723 sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides) 724 if err != nil { 725 return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID) 726 } 727 } 728 729 // If user was set, look it up in the container to get a UID to use on 730 // the host 731 if user != "" || len(sgids) > 0 { 732 if user != "" { 733 for _, sgid := range execUser.Sgids { 734 sgids = append(sgids, uint32(sgid)) 735 } 736 } 737 processUser := spec.User{ 738 UID: uint32(execUser.Uid), 739 GID: uint32(execUser.Gid), 740 AdditionalGids: sgids, 741 } 742 743 pspec.User = processUser 744 } 745 746 ctrSpec, err := c.specFromState() 747 if err != nil { 748 return nil, err 749 } 750 751 allCaps, err := capabilities.BoundingSet() 752 if err != nil { 753 return nil, err 754 } 755 if options.Privileged { 756 pspec.Capabilities.Bounding = allCaps 757 } else { 758 pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding 759 } 760 761 // Always unset the inheritable capabilities similarly to what the Linux kernel does 762 // They are used only when using capabilities with uid != 0. 763 pspec.Capabilities.Inheritable = []string{} 764 765 if execUser.Uid == 0 { 766 pspec.Capabilities.Effective = pspec.Capabilities.Bounding 767 pspec.Capabilities.Permitted = pspec.Capabilities.Bounding 768 } else { 769 if user == c.config.User { 770 pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective 771 pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective 772 pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective 773 pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective 774 } 775 } 776 777 hasHomeSet := false 778 for _, s := range pspec.Env { 779 if strings.HasPrefix(s, "HOME=") { 780 hasHomeSet = true 781 break 782 } 783 } 784 if !hasHomeSet { 785 pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) 786 } 787 788 processJSON, err := json.Marshal(pspec) 789 if err != nil { 790 return nil, err 791 } 792 793 if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { 794 return nil, err 795 } 796 return f, nil 797 }