terraform/internal/terminal/panicwrap_ugh.go

79 lines
3.0 KiB
Go

package terminal
import "os"
// This file has some annoying nonsense to, yet again, work around the
// panicwrap hack.
//
// Specifically, typically when we're running Terraform the stderr handle is
// not directly connected to the terminal but is instead a pipe into a parent
// process gathering up the output just in case a panic message appears.
// However, this package needs to know whether the _real_ stderr is connected
// to a terminal and what its width is.
//
// To work around that, we'll first initialize the terminal in the parent
// process, and then capture information about stderr into an environment
// variable so we can pass it down to the child process. The child process
// will then use the environment variable to pretend that the panicwrap pipe
// has the same characteristics as the terminal that it's indirectly writing
// to.
//
// This file has some helpers for implementing that awkward handshake, but the
// handshake itself is in package main, interspersed with all of the other
// panicwrap machinery.
//
// You might think that the code in helper/wrappedstreams could avoid this
// problem, but that package is broken on Windows: it always fails to recover
// the real stderr, and it also gets an incorrect result if the user was
// redirecting or piping stdout/stdin. So... we have this hack instead, which
// gets a correct result even on Windows and even with I/O redirection.
// StateForAfterPanicWrap is part of the workaround for panicwrap that
// captures some characteristics of stderr that the caller can pass to the
// panicwrap child process somehow and then use ReinitInsidePanicWrap.
func (s *Streams) StateForAfterPanicWrap() *PrePanicwrapState {
return &PrePanicwrapState{
StderrIsTerminal: s.Stderr.IsTerminal(),
StderrWidth: s.Stderr.Columns(),
}
}
// ReinitInsidePanicwrap is part of the workaround for panicwrap that
// produces a Streams containing a potentially-lying Stderr that might
// claim to be a terminal even if it's actually a pipe connected to the
// parent process.
//
// That's an okay lie in practice because the parent process will copy any
// data it recieves via that pipe verbatim to the real stderr anyway. (The
// original call to Init in the parent process should've already done any
// necessary modesetting on the Stderr terminal, if any.)
//
// The state argument can be nil if we're not running in panicwrap mode,
// in which case this function behaves exactly the same as Init.
func ReinitInsidePanicwrap(state *PrePanicwrapState) (*Streams, error) {
ret, err := Init()
if err != nil {
return ret, err
}
if state != nil {
// A lying stderr, then.
ret.Stderr = &OutputStream{
File: ret.Stderr.File,
isTerminal: func(f *os.File) bool {
return state.StderrIsTerminal
},
getColumns: func(f *os.File) int {
return state.StderrWidth
},
}
}
return ret, nil
}
// PrePanicwrapState is a horrible thing we use to work around panicwrap,
// related to both Streams.StateForAfterPanicWrap and ReinitInsidePanicwrap.
type PrePanicwrapState struct {
StderrIsTerminal bool
StderrWidth int
}