github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/domain/infra/abi/terminal/terminal.go (about)

     1  // +build ABISupport
     2  
     3  package terminal
     4  
     5  import (
     6  	"context"
     7  	"os"
     8  	"os/signal"
     9  
    10  	lsignal "github.com/containers/libpod/pkg/signal"
    11  	"github.com/docker/docker/pkg/term"
    12  	"github.com/pkg/errors"
    13  	"github.com/sirupsen/logrus"
    14  	"k8s.io/client-go/tools/remotecommand"
    15  )
    16  
    17  // RawTtyFormatter ...
    18  type RawTtyFormatter struct {
    19  }
    20  
    21  // getResize returns a TerminalSize command matching stdin's current
    22  // size on success, and nil on errors.
    23  func getResize() *remotecommand.TerminalSize {
    24  	winsize, err := term.GetWinsize(os.Stdin.Fd())
    25  	if err != nil {
    26  		logrus.Warnf("Could not get terminal size %v", err)
    27  		return nil
    28  	}
    29  	return &remotecommand.TerminalSize{
    30  		Width:  winsize.Width,
    31  		Height: winsize.Height,
    32  	}
    33  }
    34  
    35  // Helper for prepareAttach - set up a goroutine to generate terminal resize events
    36  func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) {
    37  	sigchan := make(chan os.Signal, 1)
    38  	signal.Notify(sigchan, lsignal.SIGWINCH)
    39  	go func() {
    40  		defer close(resize)
    41  		// Update the terminal size immediately without waiting
    42  		// for a SIGWINCH to get the correct initial size.
    43  		resizeEvent := getResize()
    44  		for {
    45  			if resizeEvent == nil {
    46  				select {
    47  				case <-ctx.Done():
    48  					return
    49  				case <-sigchan:
    50  					resizeEvent = getResize()
    51  				}
    52  			} else {
    53  				select {
    54  				case <-ctx.Done():
    55  					return
    56  				case <-sigchan:
    57  					resizeEvent = getResize()
    58  				case resize <- *resizeEvent:
    59  					resizeEvent = nil
    60  				}
    61  			}
    62  		}
    63  	}()
    64  }
    65  
    66  func restoreTerminal(state *term.State) error {
    67  	logrus.SetFormatter(&logrus.TextFormatter{})
    68  	return term.RestoreTerminal(os.Stdin.Fd(), state)
    69  }
    70  
    71  // Format ...
    72  func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    73  	textFormatter := logrus.TextFormatter{}
    74  	bytes, err := textFormatter.Format(entry)
    75  
    76  	if err == nil {
    77  		bytes = append(bytes, '\r')
    78  	}
    79  
    80  	return bytes, err
    81  }
    82  
    83  func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) {
    84  	logrus.Debugf("Handling terminal attach")
    85  
    86  	subCtx, cancel := context.WithCancel(ctx)
    87  
    88  	resizeTty(subCtx, resize)
    89  
    90  	oldTermState, err := term.SaveState(os.Stdin.Fd())
    91  	if err != nil {
    92  		// allow caller to not have to do any cleaning up if we error here
    93  		cancel()
    94  		return nil, nil, errors.Wrapf(err, "unable to save terminal state")
    95  	}
    96  
    97  	logrus.SetFormatter(&RawTtyFormatter{})
    98  	if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil {
    99  		return cancel, nil, err
   100  	}
   101  
   102  	return cancel, oldTermState, nil
   103  }