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)) }