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 }