github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terminal/panicwrap_ugh.go (about)

     1  package terminal
     2  
     3  import "os"
     4  
     5  // This file has some annoying nonsense to, yet again, work around the
     6  // panicwrap hack.
     7  //
     8  // Specifically, typically when we're running Terraform the stderr handle is
     9  // not directly connected to the terminal but is instead a pipe into a parent
    10  // process gathering up the output just in case a panic message appears.
    11  // However, this package needs to know whether the _real_ stderr is connected
    12  // to a terminal and what its width is.
    13  //
    14  // To work around that, we'll first initialize the terminal in the parent
    15  // process, and then capture information about stderr into an environment
    16  // variable so we can pass it down to the child process. The child process
    17  // will then use the environment variable to pretend that the panicwrap pipe
    18  // has the same characteristics as the terminal that it's indirectly writing
    19  // to.
    20  //
    21  // This file has some helpers for implementing that awkward handshake, but the
    22  // handshake itself is in package main, interspersed with all of the other
    23  // panicwrap machinery.
    24  //
    25  // You might think that the code in helper/wrappedstreams could avoid this
    26  // problem, but that package is broken on Windows: it always fails to recover
    27  // the real stderr, and it also gets an incorrect result if the user was
    28  // redirecting or piping stdout/stdin. So... we have this hack instead, which
    29  // gets a correct result even on Windows and even with I/O redirection.
    30  
    31  // StateForAfterPanicWrap is part of the workaround for panicwrap that
    32  // captures some characteristics of stderr that the caller can pass to the
    33  // panicwrap child process somehow and then use ReinitInsidePanicWrap.
    34  func (s *Streams) StateForAfterPanicWrap() *PrePanicwrapState {
    35  	return &PrePanicwrapState{
    36  		StderrIsTerminal: s.Stderr.IsTerminal(),
    37  		StderrWidth:      s.Stderr.Columns(),
    38  	}
    39  }
    40  
    41  // ReinitInsidePanicwrap is part of the workaround for panicwrap that
    42  // produces a Streams containing a potentially-lying Stderr that might
    43  // claim to be a terminal even if it's actually a pipe connected to the
    44  // parent process.
    45  //
    46  // That's an okay lie in practice because the parent process will copy any
    47  // data it recieves via that pipe verbatim to the real stderr anyway. (The
    48  // original call to Init in the parent process should've already done any
    49  // necessary modesetting on the Stderr terminal, if any.)
    50  //
    51  // The state argument can be nil if we're not running in panicwrap mode,
    52  // in which case this function behaves exactly the same as Init.
    53  func ReinitInsidePanicwrap(state *PrePanicwrapState) (*Streams, error) {
    54  	ret, err := Init()
    55  	if err != nil {
    56  		return ret, err
    57  	}
    58  	if state != nil {
    59  		// A lying stderr, then.
    60  		ret.Stderr = &OutputStream{
    61  			File: ret.Stderr.File,
    62  			isTerminal: func(f *os.File) bool {
    63  				return state.StderrIsTerminal
    64  			},
    65  			getColumns: func(f *os.File) int {
    66  				return state.StderrWidth
    67  			},
    68  		}
    69  	}
    70  	return ret, nil
    71  }
    72  
    73  // PrePanicwrapState is a horrible thing we use to work around panicwrap,
    74  // related to both Streams.StateForAfterPanicWrap and ReinitInsidePanicwrap.
    75  type PrePanicwrapState struct {
    76  	StderrIsTerminal bool
    77  	StderrWidth      int
    78  }