diff --git a/src/include/winpty.h b/src/include/winpty.h index 26a19ceb..09dddd83 100644 --- a/src/include/winpty.h +++ b/src/include/winpty.h @@ -86,6 +86,12 @@ WINPTY_API int winpty_get_process_id(winpty_t *pc); */ WINPTY_API HANDLE winpty_get_data_pipe(winpty_t *pc); +/* + * Returns an overlapped-mode pipe handle that can be read and written + * like a Unix terminal. + */ +WINPTY_API HANDLE winpty_get_control_pipe(winpty_t *pc); + /* * Change the size of the Windows console. */ diff --git a/src/libwinpty/winpty.cc b/src/libwinpty/winpty.cc index 58d7529f..0b2d4c21 100644 --- a/src/libwinpty/winpty.cc +++ b/src/libwinpty/winpty.cc @@ -542,6 +542,11 @@ WINPTY_API HANDLE winpty_get_data_pipe(winpty_t *pc) return pc->dataPipe; } +WINPTY_API HANDLE winpty_get_control_pipe(winpty_t *pc) +{ + return pc->controlPipe; +} + WINPTY_API int winpty_set_size(winpty_t *pc, int cols, int rows) { auto packet = newPacket(); diff --git a/src/shared/Buffer.cc b/src/shared/Buffer.cc index d566f7a4..31dd30ba 100644 --- a/src/shared/Buffer.cc +++ b/src/shared/Buffer.cc @@ -45,7 +45,7 @@ void WriteBuffer::putRawData(const void *data, size_t len) { void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) { ASSERT(pos <= m_buf.size() && len <= m_buf.size() - pos); const auto p = reinterpret_cast(data); - std::copy(p, p + len, m_buf.begin()); + std::copy(p, p + len, m_buf.begin() + pos); } void WriteBuffer::putInt32(int32_t i) { diff --git a/src/unix-adapter/ControlHandler.cc b/src/unix-adapter/ControlHandler.cc new file mode 100644 index 00000000..cb9fe301 --- /dev/null +++ b/src/unix-adapter/ControlHandler.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ControlHandler.h" + +#include +#include +#include +#include + +#include +#include + +#include "../shared/DebugClient.h" +#include "Event.h" +#include "Util.h" +#include "WakeupFd.h" + +ControlHandler::ControlHandler(HANDLE r, HANDLE w, WakeupFd &completionWakeup) : + m_read_pipe(r), + m_write_pipe(w), + m_completionWakeup(completionWakeup), + m_threadHasBeenJoined(false), + m_shouldShutdown(0), + m_threadCompleted(0) +{ + pthread_create(&m_thread, NULL, ControlHandler::threadProcS, this); +} + +void ControlHandler::shutdown() { + startShutdown(); + if (!m_threadHasBeenJoined) { + int ret = pthread_join(m_thread, NULL); + assert(ret == 0 && "pthread_join failed"); + m_threadHasBeenJoined = true; + } +} + +static BOOL read_pipe(HANDLE p, char * buf, int read_size) { + char * tmp = buf; + + while (read_size > 0) { + DWORD numRead = 0; + BOOL ret = ReadFile(p, + tmp, + read_size, + &numRead, + NULL); + + if (!ret || numRead == 0) { + return FALSE; + } + + read_size -= numRead; + tmp += numRead; + } + + return TRUE; +} + +static BOOL read_packet(HANDLE p, std::vector & buf) { + typedef unsigned __int64 uint64_t; + + uint64_t size = 0; + + char * tmp = (char *)&size; + if (!read_pipe(p, tmp, sizeof(uint64_t))) + return FALSE; + + buf.insert(buf.end(), tmp, tmp + sizeof(uint64_t)); + buf.resize(size); + + return read_pipe(p, &buf[sizeof(uint64_t)], size - sizeof(uint64_t)); +} + +void ControlHandler::threadProc() { + while (true) { + // Handle shutdown + m_wakeup.reset(); + if (m_shouldShutdown) { + trace("ControlHandler: shutting down"); + break; + } + + // Read from the pipe. + std::vector data; + + ConnectNamedPipe(m_read_pipe, NULL); + + BOOL ret = read_packet(m_read_pipe, + data); + + if (!ret) { + trace("ControlHandler: read failed: " + "ret=%d lastError=0x%x", + ret, + static_cast(GetLastError())); + break; + } + + //Write to pipe + DWORD written; + ret = WriteFile(m_write_pipe, + &data[0], data.size(), + &written, + NULL); + if (!ret || written != data.size()) { + if (!ret && GetLastError() == ERROR_BROKEN_PIPE) { + trace("ControlHandler: pipe closed: written=%u", + static_cast(written)); + } else { + trace("ControlHandler: write failed: " + "ret=%d lastError=0x%x numRead=%ld written=%u", + ret, + static_cast(GetLastError()), + static_cast(data.size()), + static_cast(written)); + } + break; + } + + DWORD numRead = 0; + ReadFile(m_write_pipe, &data[0], 4, &numRead, NULL); + WriteFile(m_read_pipe, &data[0], numRead, &written, NULL); + } + m_threadCompleted = 1; + m_completionWakeup.set(); +} diff --git a/src/unix-adapter/ControlHandler.h b/src/unix-adapter/ControlHandler.h new file mode 100644 index 00000000..090dbafb --- /dev/null +++ b/src/unix-adapter/ControlHandler.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_CONTROL_HANDLER_H +#define UNIX_ADAPTER_CONTROL_HANDLER_H + +#include +#include +#include + +#include "Event.h" +#include "WakeupFd.h" + +class ControlHandler { +public: + ControlHandler(HANDLE read_pipe, HANDLE write_pipe, WakeupFd &completionWakeup); + ~ControlHandler() { shutdown(); } + bool isComplete() { return m_threadCompleted; } + void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); } + void shutdown(); + +private: + static void *threadProcS(void *pvthis) { + reinterpret_cast(pvthis)->threadProc(); + return NULL; + } + void threadProc(); + + HANDLE m_read_pipe; + HANDLE m_write_pipe; + pthread_t m_thread; + WakeupFd &m_completionWakeup; + Event m_wakeup; + bool m_threadHasBeenJoined; + volatile sig_atomic_t m_shouldShutdown; + volatile sig_atomic_t m_threadCompleted; +}; + +#endif // UNIX_ADAPTER_CONTROL_HANDLER_H diff --git a/src/unix-adapter/InputHandler.cc b/src/unix-adapter/InputHandler.cc index 3f1bed85..bb1665a7 100644 --- a/src/unix-adapter/InputHandler.cc +++ b/src/unix-adapter/InputHandler.cc @@ -34,6 +34,8 @@ #include "Util.h" #include "WakeupFd.h" +extern bool g_pipe_mode; + InputHandler::InputHandler(HANDLE winpty, WakeupFd &completionWakeup) : m_winpty(winpty), m_completionWakeup(completionWakeup), @@ -41,7 +43,8 @@ InputHandler::InputHandler(HANDLE winpty, WakeupFd &completionWakeup) : m_shouldShutdown(0), m_threadCompleted(0) { - assert(isatty(STDIN_FILENO)); + if (!g_pipe_mode) + assert(isatty(STDIN_FILENO)); pthread_create(&m_thread, NULL, InputHandler::threadProcS, this); } diff --git a/src/unix-adapter/OutputHandler.cc b/src/unix-adapter/OutputHandler.cc index 64b5163c..b50977f0 100644 --- a/src/unix-adapter/OutputHandler.cc +++ b/src/unix-adapter/OutputHandler.cc @@ -33,6 +33,8 @@ #include "Util.h" #include "WakeupFd.h" +extern bool g_pipe_mode; + OutputHandler::OutputHandler(HANDLE winpty, WakeupFd &completionWakeup) : m_winpty(winpty), m_completionWakeup(completionWakeup), @@ -40,7 +42,8 @@ OutputHandler::OutputHandler(HANDLE winpty, WakeupFd &completionWakeup) : m_shouldShutdown(0), m_threadCompleted(0) { - assert(isatty(STDOUT_FILENO)); + if (!g_pipe_mode) + assert(isatty(STDOUT_FILENO)); pthread_create(&m_thread, NULL, OutputHandler::threadProcS, this); } diff --git a/src/unix-adapter/main.cc b/src/unix-adapter/main.cc index 863f6349..9a6eb66f 100644 --- a/src/unix-adapter/main.cc +++ b/src/unix-adapter/main.cc @@ -48,10 +48,14 @@ #include "Util.h" #include "WakeupFd.h" +#include "ControlHandler.h" + + #define CSI "\x1b[" static WakeupFd *g_mainWakeup = NULL; - +bool g_pipe_mode = false; + static WakeupFd &mainWakeup() { if (g_mainWakeup == NULL) { @@ -318,6 +322,7 @@ static void usage(const char *program, int exitCode) printf(" --mouse Enable terminal mouse input\n"); printf(" --showkey Dump STDIN escape sequences\n"); printf(" --version Show the winpty version number\n"); + printf(" --pipe run winpty in pipe mode, the stdin/stdout/stderr will be piped from other process\n"); exit(exitCode); } @@ -329,6 +334,8 @@ struct Arguments { static void parseArguments(int argc, char *argv[], Arguments &out) { out.mouseInput = false; + g_pipe_mode = false; + const char *const program = argc >= 1 ? argv[0] : ""; int argi = 1; while (argi < argc) { @@ -344,6 +351,8 @@ static void parseArguments(int argc, char *argv[], Arguments &out) } else if (arg == "--version") { dumpVersionToStdout(); exit(0); + } else if (arg == "--pipe") { + g_pipe_mode = true; } else if (arg == "--") { break; } else { @@ -401,6 +410,25 @@ static std::string formatErrorMessage(DWORD err) return msg; } +static void createControlPipe(HANDLE & r) { + DWORD pid = GetCurrentProcessId(); + + WCHAR buf[255] = {0}; + wsprintf(buf, L"\\\\.\\pipe\\winpty-%ld", pid); + + r = CreateNamedPipeW(buf, + /*dwOpenMode=*/ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_FIRST_PIPE_INSTANCE, + /*dwPipeMode=*/ + PIPE_TYPE_MESSAGE | PIPE_WAIT, + /*nMaxInstances=*/1, + /*nOutBufferSize=*/64 * 1024, + /*nInBufferSize=*/64 * 1024, + /*nDefaultTimeOut=*/3000, + NULL); +} + int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); @@ -449,7 +477,11 @@ int main(int argc, char *argv[]) } registerResizeSignalHandler(); - termios mode = setRawTerminalMode(); + + termios mode; + + if (!g_pipe_mode) + mode = setRawTerminalMode(); if (args.mouseInput) { // Start by disabling UTF-8 coordinate mode (1005), just in case we @@ -472,6 +504,22 @@ int main(int argc, char *argv[]) CSI"?1000h" CSI"?1002h" CSI"?1003h" CSI"?1015h" CSI"?1006h"); } + HANDLE control_pipe = INVALID_HANDLE_VALUE; + ControlHandler * controlInputHandler = NULL; + + if (g_pipe_mode) { + createControlPipe(control_pipe); + + if (control_pipe == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Error creating control pipe.\n"); + exit(1); + } + + controlInputHandler = new ControlHandler(control_pipe, + winpty_get_control_pipe(winpty), + mainWakeup()); + } + OutputHandler outputHandler(winpty_get_data_pipe(winpty), mainWakeup()); InputHandler inputHandler(winpty_get_data_pipe(winpty), mainWakeup()); @@ -512,8 +560,20 @@ int main(int argc, char *argv[]) CSI"?1006l" CSI"?1015l" CSI"?1003l" CSI"?1002l" CSI"?1000l"); } - restoreTerminalMode(mode); + if (!g_pipe_mode) + restoreTerminalMode(mode); winpty_close(winpty); + //control pipe may block on control pipe or winpty control pipe + //so do shutdown on everything closed + if (g_pipe_mode) { + CloseHandle(control_pipe); + + if (controlInputHandler) { + controlInputHandler->shutdown(); + delete controlInputHandler; + } + } + return exitCode; } diff --git a/src/unix-adapter/subdir.mk b/src/unix-adapter/subdir.mk index ce2cd084..26562599 100644 --- a/src/unix-adapter/subdir.mk +++ b/src/unix-adapter/subdir.mk @@ -23,6 +23,7 @@ ALL_TARGETS += build/$(UNIX_ADAPTER_EXE) $(eval $(call def_unix_target,unix-adapter,)) UNIX_ADAPTER_OBJECTS = \ + build/unix-adapter/unix-adapter/ControlHandler.o \ build/unix-adapter/unix-adapter/InputHandler.o \ build/unix-adapter/unix-adapter/OutputHandler.o \ build/unix-adapter/unix-adapter/Util.o \