(logger slog.Logger, session ssh.Session, magicTypeLabel string, cmd *exec.Cmd)
| 652 | } |
| 653 | |
| 654 | func (s *Server) startNonPTYSession(logger slog.Logger, session ssh.Session, magicTypeLabel string, cmd *exec.Cmd) error { |
| 655 | s.metrics.sessionsTotal.WithLabelValues(magicTypeLabel, "no").Add(1) |
| 656 | |
| 657 | // Create a process group and send SIGHUP to child processes, |
| 658 | // otherwise context cancellation will not propagate properly |
| 659 | // and SSH server close may be delayed. |
| 660 | cmd.SysProcAttr = cmdSysProcAttr() |
| 661 | |
| 662 | // to match OpenSSH, we don't actually tear a non-TTY command down, even if the session ends. OpenSSH closes the |
| 663 | // pipes to the process when the session ends; which is what happens here since we wire the command up to the |
| 664 | // session for I/O. |
| 665 | // c.f. https://github.com/coder/coder/issues/18519#issuecomment-3019118271 |
| 666 | cmd.Cancel = nil |
| 667 | |
| 668 | cmd.Stdout = session |
| 669 | cmd.Stderr = session.Stderr() |
| 670 | // This blocks forever until stdin is received if we don't |
| 671 | // use StdinPipe. It's unknown what causes this. |
| 672 | stdinPipe, err := cmd.StdinPipe() |
| 673 | if err != nil { |
| 674 | s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "no", "stdin_pipe").Add(1) |
| 675 | return xerrors.Errorf("create stdin pipe: %w", err) |
| 676 | } |
| 677 | go func() { |
| 678 | _, err := io.Copy(stdinPipe, session) |
| 679 | if err != nil { |
| 680 | s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "no", "stdin_io_copy").Add(1) |
| 681 | } |
| 682 | _ = stdinPipe.Close() |
| 683 | }() |
| 684 | err = cmd.Start() |
| 685 | if err != nil { |
| 686 | s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "no", "start_command").Add(1) |
| 687 | return xerrors.Errorf("start: %w", err) |
| 688 | } |
| 689 | |
| 690 | // Since we don't cancel the process when the session stops, we still need to tear it down if we are closing. So |
| 691 | // track it here. |
| 692 | if !s.trackProcess(cmd.Process, true) { |
| 693 | // must be closing |
| 694 | err = cmdCancel(logger, cmd.Process) |
| 695 | return xerrors.Errorf("failed to track process: %w", err) |
| 696 | } |
| 697 | defer s.trackProcess(cmd.Process, false) |
| 698 | |
| 699 | sigs := make(chan ssh.Signal, 1) |
| 700 | session.Signals(sigs) |
| 701 | defer func() { |
| 702 | session.Signals(nil) |
| 703 | close(sigs) |
| 704 | }() |
| 705 | go func() { |
| 706 | for sig := range sigs { |
| 707 | handleSignal(logger, sig, cmd.Process, s.metrics, magicTypeLabel) |
| 708 | } |
| 709 | }() |
| 710 | return cmd.Wait() |
| 711 | } |
no test coverage detected