agent/session/winpty/winpty.go (243 lines of code) (raw):
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// +build windows
//winpty package is wrapper package for calling procedures of winpty.dll
package winpty
import (
"fmt"
"os"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
)
type IWinPTY interface {
SetSize(ws_col, ws_row uint32) error
Close() error
}
//WinPTY contains handlers and pointers needed for launching winpty agent process
type WinPTY struct {
IWinPTY
StdIn *os.File
StdOut *os.File
agentConfig uintptr
agent uintptr
processHandle uintptr
closed bool
}
//Start launches winpty agent as a separate process
func Start(winptyDllFilePath, cmdLine string, window_size_cols, window_size_rows uint32, winptyFlag int32) (*WinPTY, error) {
var winpty WinPTY = WinPTY{}
loadDll(winptyDllFilePath)
defineProcedures()
if err := winpty.configureAgent(window_size_cols, window_size_rows, winptyFlag); err != nil {
return nil, err
}
if err := winpty.startAgent(); err != nil {
return nil, err
}
if err := winpty.getIOPipes(); err != nil {
return nil, err
}
if err := winpty.spawnProcess(cmdLine); err != nil {
return nil, err
}
return &winpty, nil
}
//configureAgent configures agent and sets initial window size.
func (winpty *WinPTY) configureAgent(window_size_cols, window_size_rows uint32, winptyFlag int32) (err error) {
var errorPtr uintptr
defer winpty_error_free.Call(errorPtr)
var lastErr error
winpty.agentConfig, _, lastErr = winpty_config_new.Call(uintptr(winptyFlag), uintptr(unsafe.Pointer(&errorPtr)))
if winpty.agentConfig == uintptr(NIL_POINTER_VALUE) {
return winpty.getFormattedErrorMessage(
"Unable to configure winpty agent.",
lastErr,
errorPtr)
}
if window_size_cols == 0 || window_size_rows == 0 {
return fmt.Errorf(
"Invalid window console size. Cannot set cols %d and rows %d",
window_size_cols,
window_size_rows)
}
winpty_config_set_initial_size.Call(
winpty.agentConfig,
uintptr(window_size_cols),
uintptr(window_size_rows))
return nil
}
//startAgent launches winpty agent.
func (winpty *WinPTY) startAgent() (err error) {
var errorPtr uintptr
defer winpty_error_free.Call(errorPtr)
var lastErr error
winpty.agent, _, lastErr = winpty_open.Call(winpty.agentConfig, uintptr(unsafe.Pointer(&errorPtr)))
if winpty.agent == uintptr(NIL_POINTER_VALUE) {
return winpty.getFormattedErrorMessage(
"Unable to launch winpty agent.",
lastErr,
errorPtr)
}
winpty_config_free.Call(winpty.agentConfig)
return nil
}
//getIOPipes gets handle for stdin and stdout.
func (winpty *WinPTY) getIOPipes() (err error) {
conin_name, _, lastErr := winpty_conin_name.Call(winpty.agent)
if conin_name == uintptr(NIL_POINTER_VALUE) {
return fmt.Errorf(
"Unable to get conin name. %s",
lastErr)
}
conout_name, _, lastErr := winpty_conout_name.Call(winpty.agent)
if conout_name == uintptr(NIL_POINTER_VALUE) {
return fmt.Errorf(
"Unable to get conout name. %s",
lastErr)
}
conin_handle, err := syscall.CreateFile(
(*uint16)(unsafe.Pointer(conin_name)),
CONIN_FILE_ACCESS,
CREATE_FILE_MODE,
nil,
CREATE_FILE_CREATE_MODE,
CREATE_FILE_ATTRS,
CREATE_FILE_TEMPLATE)
if err != nil {
return fmt.Errorf("Unable to get conin handle. %s", err)
}
winpty.StdIn = os.NewFile(uintptr(conin_handle), STDIN_FILE_NAME)
conout_handle, err := syscall.CreateFile(
(*uint16)(unsafe.Pointer(conout_name)),
CONOUT_FILE_ACCESS,
CREATE_FILE_MODE,
nil,
CREATE_FILE_CREATE_MODE,
CREATE_FILE_ATTRS,
CREATE_FILE_TEMPLATE)
if err != nil {
return fmt.Errorf("Unable to get conout handle. %s", err)
}
winpty.StdOut = os.NewFile(uintptr(conout_handle), STDOUT_FILE_NAME)
return nil
}
//spawnProcess creates a new winpty agent process.
func (winpty *WinPTY) spawnProcess(cmdLine string) (err error) {
var errorPtr uintptr
defer winpty_error_free.Call(errorPtr)
cmdLineUTF16Ptr, err := syscall.UTF16PtrFromString(cmdLine)
if err != nil {
return fmt.Errorf("Failed to convert cmd to pointer. %s", err)
}
spawnConfig, _, lastErr := winpty_spawn_config_new.Call(
uintptr(uint64(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN)),
uintptr(0),
uintptr(unsafe.Pointer(cmdLineUTF16Ptr)),
uintptr(0),
uintptr(0),
uintptr(unsafe.Pointer(&errorPtr)))
if spawnConfig == uintptr(NIL_POINTER_VALUE) {
return winpty.getFormattedErrorMessage(
"Unable to create process config.",
lastErr,
errorPtr)
}
var createProcessErr uint32
spawnProcess, _, lastErr := winpty_spawn.Call(
winpty.agent, spawnConfig,
uintptr(0),
uintptr(0),
uintptr(unsafe.Pointer(&createProcessErr)),
uintptr(unsafe.Pointer(&errorPtr)))
winpty_spawn_config_free.Call(spawnConfig)
if spawnProcess == uintptr(NIL_POINTER_VALUE) {
spawnProcessErrorCode, err := winpty.getWinptyErrorCode(errorPtr)
if err != nil {
return fmt.Errorf("Unable to get spawn process error code. %s", err)
}
if spawnProcessErrorCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED {
return fmt.Errorf("Unable to create process. %s", getWindowsErrorMessage(createProcessErr))
} else {
return winpty.getFormattedErrorMessage(
"Unable to spawn process.",
lastErr,
errorPtr)
}
}
return nil
}
//SetSize sets given console window size.
func (winpty *WinPTY) SetSize(ws_col, ws_row uint32) (err error) {
var errorPtr uintptr
defer winpty_error_free.Call(errorPtr)
if ws_col == 0 || ws_row == 0 {
return
}
sizeSetRet, _, lastErr := winpty_set_size.Call(
winpty.agent,
uintptr(ws_col),
uintptr(ws_row),
uintptr(unsafe.Pointer(&errorPtr)))
if sizeSetRet == uintptr(NIL_POINTER_VALUE) {
return winpty.getFormattedErrorMessage(
"Unable to set size.",
lastErr,
errorPtr)
}
return nil
}
//Close closes stdin, stdout and winpty process handle.
func (winpty *WinPTY) Close() (err error) {
if winpty == nil || winpty.closed {
return
}
winpty_free.Call(winpty.agent)
if winpty.StdIn != nil {
if err := winpty.StdIn.Close(); err != nil {
return fmt.Errorf("Unable to close stdin. %s", err)
}
}
if winpty.StdOut != nil {
if err := winpty.StdOut.Close(); err != nil {
return fmt.Errorf("Unable to close stdout. %s", err)
}
}
winpty.closed = true
return nil
}
//getWinptyErrorMessage returns string error message for given error pointer.
func (winpty *WinPTY) getWinptyErrorMessage(winptyErr uintptr) string {
winptyErrorMsgPtr, _, lastErr := winpty_error_msg.Call(winptyErr)
if winptyErrorMsgPtr == uintptr(NIL_POINTER_VALUE) {
return fmt.Sprintf("Unable to get error message. %s", lastErr)
}
return convertUTF16PtrToString((*uint16)(unsafe.Pointer(winptyErrorMsgPtr)))
}
//getWinptyErrorCode gets winpty error code for give error pointer.
func (winpty *WinPTY) getWinptyErrorCode(winptyErr uintptr) (errCode uint32, err error) {
winptyErrorCodePtr, _, lastErr := winpty_error_code.Call(uintptr(unsafe.Pointer(&winptyErr)))
if winptyErrorCodePtr == uintptr(NIL_POINTER_VALUE) {
return 0, winpty.getFormattedErrorMessage(
"Unable to get error code.",
lastErr,
winptyErr)
}
return *(*uint32)(unsafe.Pointer(winptyErrorCodePtr)), nil
}
//convertUTF16PtrToString converts utf16 pointer to string
func convertUTF16PtrToString(UTF16Ptr *uint16) string {
var inputStrChar uint16
outputStr := make([]uint16, 0)
inputStrPtr := unsafe.Pointer(UTF16Ptr)
for {
inputStrChar = *(*uint16)(inputStrPtr)
if inputStrChar == 0 {
return string(utf16.Decode(outputStr))
}
outputStr = append(outputStr, inputStrChar)
inputStrPtr = unsafe.Pointer(uintptr(inputStrPtr) + unsafe.Sizeof(uint16(inputStrChar)))
}
}
//getWindowsErrorMessage fetches windows error message for given error code
func getWindowsErrorMessage(errorCode uint32) string {
flags := uint32(windows.FORMAT_MESSAGE_FROM_SYSTEM | windows.FORMAT_MESSAGE_IGNORE_INSERTS)
langId := uint32(windows.SUBLANG_ENGLISH_US)<<10 | uint32(windows.LANG_ENGLISH)
buf := make([]uint16, 512)
_, err := windows.FormatMessage(flags, uintptr(0), errorCode, langId, buf, nil)
if err != nil {
return fmt.Sprintf("Unable to fetch windows error message for code %d, err: %s", errorCode, err)
}
return strings.TrimSpace(syscall.UTF16ToString(buf))
}
//getFormattedErrorMessage returns formatted error containing custom error string, syscall error and winpty error
func (winpty *WinPTY) getFormattedErrorMessage(errString string, syscallErr error, winptyErr uintptr) (err error) {
return fmt.Errorf(
"%s Error returned by syscall: %s Error returned by winpty: %s",
errString, syscallErr, winpty.getWinptyErrorMessage(winptyErr))
}