terraform/internal/terminal/impl_windows.go

163 lines
5.7 KiB
Go
Raw Normal View History

Upgrade to Go 1.17 This includes the addition of the new "//go:build" comment form in addition to the legacy "// +build" notation, as produced by gofmt to ensure consistent behavior between Go versions. The new directives are all equivalent to what was present before, so there's no change in behavior. Go 1.17 continues to use the Unicode 13 tables as in Go 1.16, so this upgrade does not require also upgrading our Unicode-related dependencies. This upgrade includes the following breaking changes which will also appear as breaking changes for Terraform users, but that are consistent with the Terraform v1.0 compatibility promises. - On MacOS, Terraform now requires macOS 10.13 High Sierra or later. This upgrade also includes the following breaking changes which will appear as breaking changes for Terraform users that are inconsistent with our compatibility promises, but have justified exceptions as follows: - cidrsubnet, cidrhost, and cidrnetmask will now reject IPv4 CIDR addresses whose decimal components have leading zeros, where previously they would just silently ignore those leading zeros. This is a security-motivated exception to our compatibility promises, because some external systems interpret zero-prefixed octets as octal numbers rather than decimal, and thus the previous lenient parsing could lead to a different interpretation of the address between systems, and thus potentially allow bypassing policy when configuring firewall rules etc. This upgrade also includes the following breaking changes which could _potentially_ appear as breaking changes for Terraform users, but that do not in practice for the reasons given: - The Go net/url package no longer allows query strings with pairs separated by semicolons instead of ampersands. This primarily affects HTTP servers written in Go, and Terraform includes a special temporary HTTP server as part of its implementation of OAuth for "terraform login", but that server only needs to accept URLs created by Terraform itself and Terraform does not generate any URLs that would be rejected.
2021-08-17 02:19:17 +02:00
//go:build windows
// +build windows
package terminal
import (
"fmt"
"os"
"syscall"
"golang.org/x/sys/windows"
// We're continuing to use this third-party library on Windows because it
// has the additional IsCygwinTerminal function, which includes some useful
// heuristics for recognizing when a pipe seems to be connected to a
// legacy terminal emulator on Windows versions that lack true pty support.
// We now use golang.org/x/term's functionality on other platforms.
isatty "github.com/mattn/go-isatty"
)
func configureOutputHandle(f *os.File) (*OutputStream, error) {
ret := &OutputStream{
File: f,
}
if fd := f.Fd(); isatty.IsTerminal(fd) {
// We have a few things to deal with here:
// - Activating UTF-8 output support (mandatory)
// - Activating virtual terminal support (optional)
// These will not succeed on Windows 8 or early versions of Windows 10.
// UTF-8 support means switching the console "code page" to CP_UTF8.
// Notice that this doesn't take the specific file descriptor, because
// the console is just ambiently associated with our process.
err := SetConsoleOutputCP(CP_UTF8)
if err != nil {
return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %s", err)
}
// If the console also allows us to turn on
// ENABLE_VIRTUAL_TERMINAL_PROCESSING then we can potentially use VT
// output, although the methods of Settings will make the final
// determination on that because we might have some handles pointing at
// terminals and other handles pointing at files/pipes.
ret.getColumns = getColumnsWindowsConsole
var mode uint32
err = windows.GetConsoleMode(windows.Handle(fd), &mode)
if err != nil {
return ret, nil // We'll treat this as success but without VT support
}
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
err = windows.SetConsoleMode(windows.Handle(fd), mode)
if err != nil {
return ret, nil // We'll treat this as success but without VT support
}
// If we get here then we've successfully turned on VT processing, so
// we can return an OutputStream that answers true when asked if it
// is a Terminal.
ret.isTerminal = staticTrue
return ret, nil
} else if isatty.IsCygwinTerminal(fd) {
// Cygwin terminals -- and other VT100 "fakers" for older versions of
// Windows -- are not really terminals in the usual sense, but rather
// are pipes between the child process (Terraform) and the terminal
// emulator. isatty.IsCygwinTerminal uses some heuristics to
// distinguish those pipes from other pipes we might see if the user
// were, for example, using the | operator on the command line.
// If we get in here then we'll assume that we can send VT100 sequences
// to this stream, even though it isn't a terminal in the usual sense.
ret.isTerminal = staticTrue
// TODO: Is it possible to detect the width of these fake terminals?
return ret, nil
}
// If we fall out here then we have a non-terminal filehandle, so we'll
// just accept all of the default OutputStream behaviors
return ret, nil
}
func configureInputHandle(f *os.File) (*InputStream, error) {
ret := &InputStream{
File: f,
}
if fd := f.Fd(); isatty.IsTerminal(fd) {
// We have to activate UTF-8 input, or else we fail. This will not
// succeed on Windows 8 or early versions of Windows 10.
// Notice that this doesn't take the specific file descriptor, because
// the console is just ambiently associated with our process.
err := SetConsoleCP(CP_UTF8)
if err != nil {
return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %s", err)
}
ret.isTerminal = staticTrue
return ret, nil
} else if isatty.IsCygwinTerminal(fd) {
// As with the output handles above, we'll use isatty's heuristic to
// pretend that a pipe from mintty or a similar userspace terminal
// emulator is actually a terminal.
ret.isTerminal = staticTrue
return ret, nil
}
// If we fall out here then we have a non-terminal filehandle, so we'll
// just accept all of the default InputStream behaviors
return ret, nil
}
func getColumnsWindowsConsole(f *os.File) int {
// We'll just unconditionally ask the given file for its console buffer
// info here, and let it fail if the file isn't actually a console.
// (In practice, the init functions above only hook up this function
// if the handle looks like a console, so this should succeed.)
var info windows.ConsoleScreenBufferInfo
err := windows.GetConsoleScreenBufferInfo(windows.Handle(f.Fd()), &info)
if err != nil {
return defaultColumns
}
return int(info.Size.X)
}
// Unfortunately not all of the Windows kernel functions we need are in
// x/sys/windows at the time of writing, so we need to call some of them
// directly. (If you're maintaining this in future and have the capacity to
// test it well, consider checking if these functions have been added upstream
// yet and switch to their wrapper stubs if so.
var modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
var procSetConsoleCP = modkernel32.NewProc("SetConsoleCP")
var procSetConsoleOutputCP = modkernel32.NewProc("SetConsoleOutputCP")
const CP_UTF8 = 65001
// (These are written in the style of the stubs in x/sys/windows, which is
// a little non-idiomatic just due to the awkwardness of the low-level syscall
// interface.)
func SetConsoleCP(codepageID uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetConsoleCP.Addr(), 1, uintptr(codepageID), 0, 0)
if r1 == 0 {
err = e1
}
return
}
func SetConsoleOutputCP(codepageID uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetConsoleOutputCP.Addr(), 1, uintptr(codepageID), 0, 0)
if r1 == 0 {
err = e1
}
return
}
func staticTrue(f *os.File) bool {
return true
}
func staticFalse(f *os.File) bool {
return false
}