Initial draft of a rewritten libwinpty with a new API.

The new API improves upon the old API in a number of ways:

 * The old API provided a single data pipe for input and output, and it
   provided the pipe in the form of a HANDLE opened with
   FILE_FLAG_OVERLAPPED.  Using a bare HANDLE is difficult in higher-level
   languages, and overlapped/asynchronous I/O is hard to get right.
   winpty_close closed the data pipe, which also didn't help things, though
   I think the handle could have been duplicated.

   Using a single handle for input and output complicates shutdown.  When
   the child process exits, the agent scrapes the final output, then closes
   the data pipe once its written.  It's possible that a winpty client will
   first detect the closed pipe when it's writing *input* rather than
   reading output, which seems wrong.  (On the other hand, the agent
   doesn't implement "backpressure" for either input or output (yet), and
   if it did, it's possible that post-exit shutdown should interrupt a
   blocked write into the console input queue.  I need to think about it
   more.)

   The new API splits the data pipe into CONIN and CONOUT pipes, which are
   accessed by filename.  After `winpty_open` returns, the client queries
   pipe names using `winpty_conin_name` and `winpty_conout_name`, then
   opens them using any mechanism, low-level or high-level, blocking or
   overlapped.

   (There are still many open design issues in this part of the API.)

 * The old libwinpty handled errors by killing the process.  The new
   libwinpty uses C++ exceptions internally and translates exceptions at
   API boundaries using:

    - a boolean error result (e.g. BOOL, NULL-vs-non-NULL)
    - a potentially heap-allocated winpty_error_t object returned via an
      optional winpty_error_ptr_t parameter.  That parameter can be NULL.
      If it isn't, then an error code and message can be queried from the
      error object.  The winpty_error_t object must be freed with
      winpty_error_free.

 * winpty_start_process is renamed to winpty_spawn.  winpty_open and
   winpty_spawn accept a "config" object which holds parameters.  New
   configuration options can be added without disturbing the source or
   binary API.  There are various flags I'm planning to add, which are
   currently documented but not implemented.

 * The winpty_get_exit_code and winpty_get_process_id APIs are removed.
   The winpty_spawn function has an out parameter providing the child's
   process and thread HANDLEs (duplicated from the agent via
   DuplicateHandle).  The winpty client can call GetExitCodeProcess and
   GetProcessId (as well as the WaitForXXX APIs to wait for child exit).

As of this libwinpty rewrite, all code outside the UNIX adapter uses a
subset of C++11.  The code will build with MSVC2013 or newer.  The oldest
MinGW G++ I see on my machine is the somewhat broken MinGW (not MinGW-w64)
compiler distributed with Cygwin.  That compiler is G++ 4.7.3, which is
new enough.
This commit is contained in:
Ryan Prichard 2016-01-04 21:05:02 -06:00
parent d72f4a44f8
commit 6b31bd9d8c
28 changed files with 2026 additions and 724 deletions

View File

@ -45,9 +45,8 @@ UNIX_CXXFLAGS += \
MINGW_CXXFLAGS += \
$(COMMON_CXXFLAGS) \
-fno-exceptions \
-fno-rtti \
-O2
-O2 \
-std=c++11
MINGW_LDFLAGS += -static -static-libgcc -static-libstdc++
UNIX_LDFLAGS += $(UNIX_LDFLAGS_STATIC)

View File

@ -19,28 +19,40 @@
// IN THE SOFTWARE.
#include "Agent.h"
#include "Win32Console.h"
#include "ConsoleInput.h"
#include "Terminal.h"
#include "NamedPipe.h"
#include "ConsoleFont.h"
#include "../shared/DebugClient.h"
#include "../shared/AgentMsg.h"
#include "../shared/Buffer.h"
#include "../shared/c99_snprintf.h"
#include "../shared/WinptyAssert.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <windows.h>
#include <vector>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "../shared/AgentMsg.h"
#include "../shared/Buffer.h"
#include "../shared/DebugClient.h"
#include "../shared/WinptyAssert.h"
#include "../shared/c99_snprintf.h"
#include "ConsoleFont.h"
#include "ConsoleInput.h"
#include "NamedPipe.h"
#include "Terminal.h"
#include "Win32Console.h"
// Work around a bug with mingw-gcc-g++. mingw-w64 is unaffected. See
// GitHub issue 27.
#ifndef FILE_FLAG_FIRST_PIPE_INSTANCE
#define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
#endif
const int SC_CONSOLE_MARK = 0xFFF2;
const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
static volatile LONG g_pipeCounter;
#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
namespace {
@ -113,12 +125,13 @@ static bool detectWhetherMarkMovesCursor(Win32Console &console)
} // anonymous namespace
Agent::Agent(LPCWSTR controlPipeName,
LPCWSTR dataPipeName,
Agent::Agent(const std::wstring &controlPipeName,
DWORD agentStartupFlags,
int initialCols,
int initialRows) :
m_agentStartupFlags(agentStartupFlags),
m_useMark(false),
m_closingDataSocket(false),
m_closingConoutPipe(false),
m_terminal(NULL),
m_childProcess(NULL),
m_childExitCode(-1),
@ -146,9 +159,31 @@ Agent::Agent(LPCWSTR controlPipeName,
m_console->setTextAttribute(7);
m_console->clearAllLines(m_console->bufferInfo());
m_controlSocket = makeSocket(controlPipeName);
m_dataSocket = makeSocket(dataPipeName);
m_terminal = new Terminal(m_dataSocket);
m_controlSocket = connectToNamedPipe(controlPipeName);
m_coninPipe = makeDataPipe(false);
m_conoutPipe = makeDataPipe(true);
{
// Send an initial response packet to winpty.dll containing pipe names.
WriteBuffer packet;
packet.putRawInt32(0); // payload size
packet.putWString(m_coninPipe->name());
packet.putWString(m_conoutPipe->name());
packet.replaceRawInt32(0, packet.buf().size() - sizeof(int));
const auto bytes = packet.buf();
m_controlSocket->writeImmediately(bytes.data(), bytes.size());
// Wait until our I/O pipes have been connected. We can't enter the main
// I/O loop until we've connected them, because we can't do normal reads
// and writes until then.
trace("Agent startup: waiting for client to connect to "
"CONIN/CONOUT pipes...");
m_coninPipe->connectToClient();
m_conoutPipe->connectToClient();
trace("Agent startup: CONIN/CONOUT pipes connected");
}
m_terminal = new Terminal(m_conoutPipe);
m_consoleInput = new ConsoleInput(this);
resetConsoleTracking(Terminal::OmitClear, m_console->windowRect());
@ -194,20 +229,52 @@ Agent::~Agent()
// bytes before it are complete keypresses.
void Agent::sendDsr()
{
m_dataSocket->write("\x1B[6n");
m_conoutPipe->write("\x1B[6n");
}
NamedPipe *Agent::makeSocket(LPCWSTR pipeName)
// Connect to the existing named pipe.
NamedPipe *Agent::connectToNamedPipe(const std::wstring &pipeName)
{
NamedPipe *pipe = createNamedPipe();
if (!pipe->connectToServer(pipeName)) {
trace("error: could not connect to %ls", pipeName);
::exit(1);
trace("error: could not connect to %ls", pipeName.c_str());
abort();
}
pipe->setReadBufferSize(64 * 1024);
return pipe;
}
// Returns a new server named pipe. It has not yet been connected.
NamedPipe *Agent::makeDataPipe(bool write)
{
std::wstringstream nameSS;
nameSS << L"\\\\.\\pipe\\winpty-data-"
<< (write ? L"out-" : L"in-")
<< GetCurrentProcessId() << L"-"
<< InterlockedIncrement(&g_pipeCounter);
const auto name = nameSS.str();
const DWORD openMode =
(write ? PIPE_ACCESS_OUTBOUND : PIPE_ACCESS_INBOUND)
| FILE_FLAG_FIRST_PIPE_INSTANCE
| FILE_FLAG_OVERLAPPED;
HANDLE ret = CreateNamedPipeW(name.c_str(),
/*dwOpenMode=*/openMode,
/*dwPipeMode=*/0,
/*nMaxInstances=*/1,
/*nOutBufferSize=*/(write ? 8192 : 0),
/*nInBufferSize=*/(write ? 0 : 256),
/*nDefaultTimeOut=*/30000,
nullptr);
if (ret == INVALID_HANDLE_VALUE) {
trace("error: could not open data pipe %ls", name.c_str());
abort();
}
NamedPipe *pipe = createNamedPipe();
pipe->adoptHandle(ret, write, name);
pipe->setReadBufferSize(64 * 1024);
return pipe;
}
void Agent::resetConsoleTracking(
Terminal::SendClearFlag sendClear, const SmallRect &windowRect)
{
@ -228,10 +295,13 @@ void Agent::resetConsoleTracking(
void Agent::onPipeIo(NamedPipe *namedPipe)
{
if (namedPipe == m_controlSocket)
if (namedPipe == m_conoutPipe) {
pollConoutPipe();
} else if (namedPipe == m_coninPipe) {
pollConinPipe();
} else if (namedPipe == m_controlSocket) {
pollControlSocket();
else if (namedPipe == m_dataSocket)
pollDataSocket();
}
}
void Agent::pollControlSocket()
@ -253,24 +323,20 @@ void Agent::pollControlSocket()
m_controlSocket->setReadBufferSize(totalSize);
break;
}
std::string packetData = m_controlSocket->read(totalSize);
ASSERT((int)packetData.size() == totalSize);
ReadBuffer buffer(packetData);
buffer.getInt(); // Discard the size.
auto packetData = m_controlSocket->readAsVector(totalSize);
ASSERT(packetData.size() == static_cast<size_t>(totalSize));
ReadBuffer buffer(std::move(packetData), ReadBuffer::NoThrow);
buffer.getRawInt32(); // Discard the size.
handlePacket(buffer);
}
}
void Agent::handlePacket(ReadBuffer &packet)
{
int type = packet.getInt();
int32_t result = -1;
int type = packet.getInt32();
switch (type) {
case AgentMsg::Ping:
result = 0;
break;
case AgentMsg::StartProcess:
result = handleStartProcessPacket(packet);
handleStartProcessPacket(packet);
break;
case AgentMsg::SetSize:
// TODO: I think it might make sense to collapse consecutive SetSize
@ -278,42 +344,53 @@ void Agent::handlePacket(ReadBuffer &packet)
// messages faster than they can be processed, and some GUIs might
// generate a flood of them, so if we can read multiple SetSize packets
// at once, we can ignore the early ones.
result = handleSetSizePacket(packet);
break;
case AgentMsg::GetExitCode:
ASSERT(packet.eof());
result = m_childExitCode;
break;
case AgentMsg::GetProcessId:
ASSERT(packet.eof());
if (m_childProcess == NULL)
result = -1;
else
result = GetProcessId(m_childProcess);
break;
case AgentMsg::SetConsoleMode:
m_terminal->setConsoleMode(packet.getInt());
result = 0;
handleSetSizePacket(packet);
break;
default:
trace("Unrecognized message, id:%d", type);
abort();
}
m_controlSocket->write((char*)&result, sizeof(result));
}
int Agent::handleStartProcessPacket(ReadBuffer &packet)
void Agent::writePacket(WriteBuffer &packet)
{
const auto &bytes = packet.buf();
packet.replaceRawInt32(0, bytes.size() - sizeof(int));
m_controlSocket->write(bytes.data(), bytes.size());
}
static HANDLE duplicateHandle(HANDLE h) {
HANDLE ret = nullptr;
if (!DuplicateHandle(
GetCurrentProcess(), h,
GetCurrentProcess(), &ret,
0, FALSE, DUPLICATE_SAME_ACCESS)) {
ASSERT(false && "DuplicateHandle failed!");
}
return ret;
}
static int64_t int64FromHandle(HANDLE h) {
return static_cast<int64_t>(reinterpret_cast<uintptr_t>(h));
}
void Agent::handleStartProcessPacket(ReadBuffer &packet)
{
BOOL success;
ASSERT(m_childProcess == NULL);
std::wstring program = packet.getWString();
std::wstring cmdline = packet.getWString();
std::wstring cwd = packet.getWString();
std::wstring env = packet.getWString();
std::wstring desktop = packet.getWString();
ASSERT(packet.eof());
const DWORD winptyFlags = packet.getInt32();
const bool wantProcessHandle = packet.getInt32();
const bool wantThreadHandle = packet.getInt32();
const std::wstring program = packet.getWString();
const std::wstring cmdline = packet.getWString();
const std::wstring cwd = packet.getWString();
const std::wstring env = packet.getWString();
const std::wstring desktop = packet.getWString();
packet.assertEof();
LPCWSTR programArg = program.empty() ? NULL : program.c_str();
// TODO: libwinpty has a modifiableWString Util function that does this.
// Factor out...
std::vector<wchar_t> cmdlineCopy;
LPWSTR cmdlineArg = NULL;
if (!cmdline.empty()) {
@ -325,44 +402,64 @@ int Agent::handleStartProcessPacket(ReadBuffer &packet)
LPCWSTR cwdArg = cwd.empty() ? NULL : cwd.c_str();
LPCWSTR envArg = env.empty() ? NULL : env.data();
STARTUPINFOW sui;
PROCESS_INFORMATION pi;
memset(&sui, 0, sizeof(sui));
memset(&pi, 0, sizeof(pi));
STARTUPINFOW sui = {};
PROCESS_INFORMATION pi = {};
sui.cb = sizeof(sui);
sui.lpDesktop = desktop.empty() ? NULL : (LPWSTR)desktop.c_str();
success = CreateProcessW(programArg, cmdlineArg, NULL, NULL,
const BOOL success = CreateProcessW(programArg, cmdlineArg, NULL, NULL,
/*bInheritHandles=*/FALSE,
/*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT |
/*CREATE_NEW_PROCESS_GROUP*/0,
/*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT,
(LPVOID)envArg, cwdArg, &sui, &pi);
int ret = success ? 0 : GetLastError();
const int lastError = success ? 0 : GetLastError();
trace("CreateProcess: %s %d",
(success ? "success" : "fail"),
(int)pi.dwProcessId);
int64_t replyProcess = 0;
int64_t replyThread = 0;
if (success) {
if (wantProcessHandle) {
replyProcess = int64FromHandle(duplicateHandle(pi.hProcess));
}
if (wantThreadHandle) {
replyThread = int64FromHandle(duplicateHandle(pi.hThread));
}
CloseHandle(pi.hThread);
// TODO: Respect the WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN flag. Keep a
// list of process handles where the flag was set; if any die, then
// shutdown and close all the handles.
m_childProcess = pi.hProcess;
}
return ret;
// Write reply.
WriteBuffer reply;
reply.putRawInt32(0); // payload size
reply.putInt(!success);
reply.putInt(lastError);
reply.putInt64(replyProcess);
reply.putInt64(replyThread);
writePacket(reply);
}
int Agent::handleSetSizePacket(ReadBuffer &packet)
void Agent::handleSetSizePacket(ReadBuffer &packet)
{
int cols = packet.getInt();
int rows = packet.getInt();
ASSERT(packet.eof());
packet.assertEof();
resizeWindow(cols, rows);
return 0;
WriteBuffer reply;
reply.putRawInt32(0); // payload size
writePacket(reply);
}
void Agent::pollDataSocket()
void Agent::pollConinPipe()
{
const std::string newData = m_dataSocket->readAll();
const std::string newData = m_coninPipe->readAll();
if (hasDebugFlag("input_separated_bytes")) {
// This debug flag is intended to help with testing incomplete escape
// sequences and multibyte UTF-8 encodings. (I wonder if the normal
@ -373,14 +470,17 @@ void Agent::pollDataSocket()
} else {
m_consoleInput->writeInput(newData);
}
}
void Agent::pollConoutPipe()
{
// If the child process had exited, then close the data socket if we've
// finished sending all of the collected output.
if (m_closingDataSocket &&
!m_dataSocket->isClosed() &&
m_dataSocket->bytesToSend() == 0) {
trace("Closing data pipe after data is sent");
m_dataSocket->closePipe();
if (m_closingConoutPipe &&
!m_conoutPipe->isClosed() &&
m_conoutPipe->bytesToSend() == 0) {
trace("Closing CONOUT pipe after data is sent");
m_conoutPipe->closePipe();
}
}
@ -412,6 +512,9 @@ void Agent::onPollTimeout()
m_consoleInput->flushIncompleteEscapeCode();
// Check if the child process has exited.
// TODO: We're potentially calling WaitForSingleObject on a NULL m_childProcess, I think.
// TODO: More importantly, we're *polling* for process exit. We have a HANDLE that we
// could wait on! It would improve responsiveness.
if (WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) {
DWORD exitCode;
if (GetExitCodeProcess(m_childProcess, &exitCode))
@ -422,20 +525,20 @@ void Agent::onPollTimeout()
// Close the data socket to signal to the client that the child
// process has exited. If there's any data left to send, send it
// before closing the socket.
m_closingDataSocket = true;
m_closingConoutPipe = true;
}
// Scrape for output *after* the above exit-check to ensure that we collect
// the child process's final output.
if (!m_dataSocket->isClosed()) {
if (!m_conoutPipe->isClosed()) {
syncConsoleContentAndSize(false);
}
if (m_closingDataSocket &&
!m_dataSocket->isClosed() &&
m_dataSocket->bytesToSend() == 0) {
trace("Closing data pipe after child exit");
m_dataSocket->closePipe();
if (m_closingConoutPipe &&
!m_conoutPipe->isClosed() &&
m_conoutPipe->bytesToSend() == 0) {
trace("Closing CONOUT pipe after child exit");
m_conoutPipe->closePipe();
}
}
@ -649,7 +752,7 @@ void Agent::syncConsoleTitle()
if (newTitle != m_currentTitle) {
std::string command = std::string("\x1b]0;") +
wstringToUtf8String(newTitle) + "\x07";
m_dataSocket->write(command.c_str());
m_conoutPipe->write(command.c_str());
m_currentTitle = newTitle;
}
}

View File

@ -38,6 +38,7 @@
class Win32Console;
class ConsoleInput;
class ReadBuffer;
class WriteBuffer;
class NamedPipe;
struct ConsoleScreenBufferInfo;
@ -51,24 +52,27 @@ const int SYNC_MARKER_LEN = 16;
class Agent : public EventLoop, public DsrSender
{
public:
Agent(LPCWSTR controlPipeName,
LPCWSTR dataPipeName,
Agent(const std::wstring &controlPipeName,
DWORD agentStartupFlags,
int initialCols,
int initialRows);
virtual ~Agent();
void sendDsr();
private:
NamedPipe *makeSocket(LPCWSTR pipeName);
NamedPipe *connectToNamedPipe(const std::wstring &pipeName);
NamedPipe *makeDataPipe(bool write);
void resetConsoleTracking(
Terminal::SendClearFlag sendClear, const SmallRect &windowRect);
private:
void pollControlSocket();
void handlePacket(ReadBuffer &packet);
int handleStartProcessPacket(ReadBuffer &packet);
int handleSetSizePacket(ReadBuffer &packet);
void pollDataSocket();
void writePacket(WriteBuffer &packet);
void handleStartProcessPacket(ReadBuffer &packet);
void handleSetSizePacket(ReadBuffer &packet);
void pollConinPipe();
void pollConoutPipe();
void updateMouseInputFlags(bool forceTrace=false);
protected:
@ -93,13 +97,15 @@ private:
void createSyncMarker(int row);
private:
DWORD m_agentStartupFlags;
bool m_useMark;
Win32Console *m_console;
bool m_consoleMouseInputEnabled;
bool m_consoleQuickEditEnabled;
NamedPipe *m_controlSocket;
NamedPipe *m_dataSocket;
bool m_closingDataSocket;
bool m_closingConoutPipe;
NamedPipe *m_coninPipe;
NamedPipe *m_conoutPipe;
Terminal *m_terminal;
ConsoleInput *m_consoleInput;
HANDLE m_childProcess;

View File

@ -43,18 +43,27 @@ NamedPipe::~NamedPipe()
// Returns true if anything happens (data received, data sent, pipe error).
bool NamedPipe::serviceIo(std::vector<HANDLE> *waitHandles)
{
if (m_handle == NULL)
if (m_handle == NULL) {
return false;
int readBytes = m_inputWorker->service();
int writeBytes = m_outputWorker->service();
}
int readBytes = 0;
int writeBytes = 0;
HANDLE readHandle = NULL;
HANDLE writeHandle = NULL;
if (m_inputWorker != NULL) {
readBytes = m_inputWorker->service();
readHandle = m_inputWorker->getWaitEvent();
}
if (m_outputWorker != NULL) {
writeBytes = m_outputWorker->service();
writeHandle = m_outputWorker->getWaitEvent();
}
if (readBytes == -1 || writeBytes == -1) {
closePipe();
return true;
}
if (m_inputWorker->getWaitEvent() != NULL)
waitHandles->push_back(m_inputWorker->getWaitEvent());
if (m_outputWorker->getWaitEvent() != NULL)
waitHandles->push_back(m_outputWorker->getWaitEvent());
if (readHandle != NULL) { waitHandles->push_back(readHandle); }
if (writeHandle != NULL) { waitHandles->push_back(writeHandle); }
return readBytes > 0 || writeBytes > 0;
}
@ -180,17 +189,19 @@ int NamedPipe::OutputWorker::getPendingIoSize()
return m_pending ? m_currentIoSize : 0;
}
bool NamedPipe::connectToServer(LPCWSTR pipeName)
// Connect to an existing named pipe.
bool NamedPipe::connectToServer(const std::wstring &name)
{
ASSERT(isClosed());
HANDLE handle = CreateFileW(pipeName,
m_name = name;
HANDLE handle = CreateFileW(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
trace("connection to [%ls], handle == 0x%x", pipeName, handle);
trace("connection to [%ls], handle == 0x%x", name.c_str(), handle);
if (handle == INVALID_HANDLE_VALUE)
return false;
m_handle = handle;
@ -199,6 +210,60 @@ bool NamedPipe::connectToServer(LPCWSTR pipeName)
return true;
}
// Block until the server pipe is connected to a client, or kill the agent
// process if the connect times out.
void NamedPipe::connectToClient()
{
ASSERT(!isClosed());
OVERLAPPED over = {};
over.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
ASSERT(over.hEvent != NULL);
BOOL success = ConnectNamedPipe(m_handle, &over);
if (!success && GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(over.hEvent, 30000);
DWORD actual = 0;
success = GetOverlappedResult(m_handle, &over, &actual, FALSE);
}
if (!success && GetLastError() == ERROR_PIPE_CONNECTED) {
success = true;
}
ASSERT(success && "error connecting data I/O pipe");
CloseHandle(over.hEvent);
}
// Bypass the output queue and event loop. Block until the data is written,
// or kill the agent process if the write times out.
void NamedPipe::writeImmediately(const void *data, int size)
{
ASSERT(m_outputWorker != NULL);
ASSERT(!m_outputWorker->ioPending());
OVERLAPPED over = {};
over.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
ASSERT(over.hEvent != NULL);
DWORD actual = 0;
BOOL success = WriteFile(m_handle, data, size, &actual, &over);
if (!success && GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(over.hEvent, 30000);
success = GetOverlappedResult(m_handle, &over, &actual, FALSE);
}
ASSERT(success && actual == static_cast<DWORD>(size) &&
"error writing data to pipe");
CloseHandle(over.hEvent);
}
// Adopt a handle for an already-open named pipe instance.
void NamedPipe::adoptHandle(HANDLE handle, bool write, const std::wstring &name)
{
ASSERT(isClosed());
m_name = name;
m_handle = handle;
if (write) {
m_outputWorker = new OutputWorker(this);
} else {
m_inputWorker = new InputWorker(this);
}
}
int NamedPipe::bytesToSend()
{
int ret = m_outQueue.size();
@ -247,6 +312,18 @@ std::string NamedPipe::read(int size)
return ret;
}
std::vector<char> NamedPipe::readAsVector(int size)
{
const auto retSize = std::min<size_t>(size, m_inQueue.size());
std::vector<char> ret(retSize);
if (retSize > 0) {
const char *const p = &m_inQueue[0];
std::copy(p, p + retSize, ret.begin());
m_inQueue.erase(0, retSize);
}
return ret;
}
std::string NamedPipe::readAll()
{
std::string ret = m_inQueue;
@ -256,11 +333,16 @@ std::string NamedPipe::readAll()
void NamedPipe::closePipe()
{
if (m_handle == NULL)
if (m_handle == NULL) {
return;
}
CancelIo(m_handle);
m_inputWorker->waitForCanceledIo();
m_outputWorker->waitForCanceledIo();
if (m_inputWorker != NULL) {
m_inputWorker->waitForCanceledIo();
}
if (m_outputWorker != NULL) {
m_outputWorker->waitForCanceledIo();
}
delete m_inputWorker;
delete m_outputWorker;
CloseHandle(m_handle);

View File

@ -45,6 +45,7 @@ private:
int service();
void waitForCanceledIo();
HANDLE getWaitEvent();
bool ioPending() { return m_pending; }
protected:
NamedPipe *m_namedPipe;
bool m_pending;
@ -77,7 +78,11 @@ private:
};
public:
bool connectToServer(LPCWSTR pipeName);
const std::wstring &name() { return m_name; }
bool connectToServer(const std::wstring &name);
void connectToClient();
void writeImmediately(const void *data, int size);
void adoptHandle(HANDLE handle, bool write, const std::wstring &name);
int bytesToSend();
void write(const void *data, int size);
void write(const char *text);
@ -86,12 +91,14 @@ public:
int bytesAvailable();
int peek(void *data, int size);
std::string read(int size);
std::vector<char> readAsVector(int size);
std::string readAll();
void closePipe();
bool isClosed();
private:
// Input/output buffers
std::wstring m_name;
int m_readBufferSize;
std::string m_inQueue;
std::string m_outQueue;

View File

@ -27,7 +27,7 @@
#include "../shared/WinptyVersion.h"
const char USAGE[] =
"Usage: %s controlPipeName dataPipeName cols rows\n"
"Usage: %s controlPipeName flags cols rows\n"
"\n"
"Ordinarily, this program is launched by winpty.dll and is not directly\n"
"useful to winpty users. However, it also has options intended for\n"
@ -72,7 +72,7 @@ int main(int argc, char *argv[])
}
Agent agent(heapMbsToWcs(argv[1]),
heapMbsToWcs(argv[2]),
atoi(argv[2]),
atoi(argv[3]),
atoi(argv[4]));
agent.run();

View File

@ -36,6 +36,7 @@ AGENT_OBJECTS = \
build/mingw/agent/Terminal.o \
build/mingw/agent/Win32Console.o \
build/mingw/agent/main.o \
build/mingw/shared/Buffer.o \
build/mingw/shared/DebugClient.o \
build/mingw/shared/WinptyAssert.o \
build/mingw/shared/WinptyVersion.o \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2012 Ryan Prichard
* 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
@ -23,9 +23,13 @@
#ifndef WINPTY_H
#define WINPTY_H
#include <stdlib.h>
#include <windows.h>
/* On 32-bit Windows, winpty functions have the default __cdecl (not __stdcall)
* calling convention. (64-bit Windows has only a single calling convention.)
* When compiled with __declspec(dllexport), with either MinGW or MSVC, the
* winpty functions are unadorned--no underscore prefix or '@nn' suffix--so
* GetProcAddress can be used easily. */
#ifdef COMPILING_WINPTY_DLL
#define WINPTY_API __declspec(dllexport)
#else
@ -36,70 +40,232 @@
extern "C" {
#endif
/* The winpty API uses wide characters, instead of UTF-8, to avoid conversion
* complications related to surrogates. Windows generally tolerates unpaired
* surrogates in text, which makes conversion to and from UTF-8 ambiguous and
* complicated. (There are different UTF-8 variants that deal with UTF-16
* surrogates differently.)
*
* This header chooses WCHAR over wchar_t in case wchar_t is somehow
* 32-bits. */
/*****************************************************************************
* Error handling. */
/* All the APIs have an optional winpty_error_t output parameter. If a
* non-NULL argument is specified, then either the API writes NULL to the
* value (on success) or writes a newly allocated winpty_error_t object. The
* object must be freed using winpty_error_free. */
/* An error object. */
typedef struct winpty_error_s winpty_error_t;
typedef winpty_error_t *winpty_error_ptr_t;
/* An error code -- one of WINPTY_ERROR_xxx. */
typedef DWORD winpty_result_t;
/* Gets the error code from the error object. */
WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err);
/* Returns a textual representation of the error. The string is freed when
* the error is freed. */
WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err);
/* Free the error object. Every error returned from the winpty API must be
* freed. */
WINPTY_API void winpty_error_free(winpty_error_ptr_t err);
#define WINPTY_ERROR_OUT_OF_MEMORY 1
#define WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED 2
#define WINPTY_ERROR_INVALID_ARGUMENT 3
#define WINPTY_ERROR_LOST_CONNECTION 4
#define WINPTY_ERROR_AGENT_EXE_MISSING 5
#define WINPTY_ERROR_WINDOWS_ERROR 6
#define WINPTY_ERROR_INTERNAL_ERROR 7
#define WINPTY_ERROR_AGENT_DIED 8
#define WINPTY_ERROR_AGENT_TIMEOUT 9
#define WINPTY_ERROR_AGENT_CREATION_FAILED 10
/*****************************************************************************
* Configuration of a new agent. */
/* The winpty_config_t object is not thread-safe. */
typedef struct winpty_config_s winpty_config_t;
/* Enable "plain text mode". In this mode, winpty avoids outputting escape
* sequences. It tries to generate output suitable to situations where a full
* terminal isn't available. (e.g. an IDE pops up a window for authenticating
* an SVN connection.) */
#define WINPTY_FLAG_PLAIN_TEXT 1
/* On XP and Vista, winpty needs to put the hidden console on a desktop in a
* service window station so that its polling does not interfere with other
* (visible) console windows. To create this desktop, it must change the
* process' window station (i.e. SetProcessWindowStation) for the duration of
* the winpty_open call. In theory, this change could interfere with the
* winpty client (e.g. other threads, spawning children), so winpty by default
* tasks a special agent with creating the hidden desktop. Spawning processes
* on Windows is slow, though, so if WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION
* is set, winpty changes this process' window station instead.
* See https://github.com/rprichard/winpty/issues/58. */
#define WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION 2
/* Ordinarilly, the agent closes its attached console as it exits, which
* prompts Windows to kill all the processes attached to the console. Specify
* this flag to suppress this behavior. */
#define WINPTY_FLAG_LEAVE_CONSOLE_OPEN_ON_EXIT 4
/* All the winpty flags. */
#define WINPTY_FLAG_MASK (0 \
| WINPTY_FLAG_PLAIN_TEXT \
| WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \
| WINPTY_FLAG_LEAVE_CONSOLE_OPEN_ON_EXIT \
)
/* Allocate a winpty_config_t value. Returns NULL on error. There are no
* required settings -- the object may immediately be used. Unrecognized flags
* are an error. */
WINPTY_API winpty_config_t *
winpty_config_new(DWORD flags, winpty_error_ptr_t *err /*OPTIONAL*/);
/* Free the cfg object after passing it to winpty_open. */
WINPTY_API void winpty_config_free(winpty_config_t *cfg);
WINPTY_API BOOL
winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows,
winpty_error_ptr_t *err /*OPTIONAL*/);
/* Amount of time to wait for the agent to startup and to wait for any given
* agent RPC request. Must be greater than 0. Can be INFINITE. */
WINPTY_API BOOL
winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs,
winpty_error_ptr_t *err /*OPTIONAL*/);
/*****************************************************************************
* Start the agent. */
/* The winpty_t object is thread-safe. */
typedef struct winpty_s winpty_t;
/*
* winpty API.
*/
/* Starts the agent. Returns NULL on error. This process will connect to the
* agent over a control pipe, and the agent will open CONIN and CONOUT server
* pipes. The agent blocks until these pipes are connected, so the client must
* connect to them before invoking an agent RPC (or else deadlock). */
WINPTY_API winpty_t *
winpty_open(const winpty_config_t *cfg,
winpty_error_ptr_t *err /*OPTIONAL*/);
/*
* Starts a new winpty instance with the given size.
/* A handle to the agent process. This value is valid for the lifetime of the
* winpty_t object. Do not close it. */
WINPTY_API HANDLE winpty_agent_process(winpty_t *wp);
/*****************************************************************************
* I/O pipes. */
/* Returns the names of named pipes used for terminal I/O. Each input or
* output direction uses a different half-duplex pipe. The agent creates
* these pipes, and the client can connect to them using ordinary I/O methods.
* The strings are freed when the winpty_t object is freed.
*
* This function creates a new agent process and connects to it.
*/
WINPTY_API winpty_t *winpty_open(int cols, int rows);
* N.B.: CreateFile does not block when connecting to a local server pipe. If
* the server pipe does not exist or is already connected, then it fails
* instantly. */
WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp);
WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp);
/*
* Start a child process. Either (but not both) of appname and cmdline may
* be NULL. cwd and env may be NULL. env is a pointer to an environment
* block like that passed to CreateProcess.
/*****************************************************************************
* winpty agent RPC call: process creation. */
/* The winpty_spawn_config_t object is not thread-safe. */
typedef struct winpty_spawn_config_s winpty_spawn_config_t;
/* If the spawn is marked "auto-shutdown", then the agent shuts down console
* output once the process exits. See winpty_shutdown_output. */
#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1
/* All the spawn flags. */
#define WINPTY_SPAWN_FLAG_MASK (0 \
| WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN \
)
/* winpty_spawn_config strings do not need to live as long as the config
* object. They are copied. Returns NULL on error. An unrecognized flag is
* an error.
*
* This function never modifies the cmdline, unlike CreateProcess.
* env is a a pointer to an environment block like that passed to
* CreateProcess--a contiguous array of NUL-terminated "VAR=VAL" strings
* followed by a final NUL terminator. */
WINPTY_API winpty_spawn_config_t *
winpty_spawn_config_new(DWORD winptyFlags,
LPCWSTR appname /*OPTIONAL*/,
LPCWSTR cmdline /*OPTIONAL*/,
LPCWSTR cwd /*OPTIONAL*/,
LPCWSTR env /*OPTIONAL*/,
winpty_error_ptr_t *err /*OPTIONAL*/);
/* Free the cfg object after passing it to winpty_spawn. */
WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg);
/*
* Spawns the new process.
*
* Only one child process may be started. After the child process exits, the
* agent will scrape the console output one last time, then close the data pipe
* once all remaining data has been sent.
* The function initializes all output parameters to zero or NULL.
*
* Returns 0 on success or a Win32 error code on failure.
* On success, the function returns TRUE. For each of process_handle and
* thread_handle that is non-NULL, the HANDLE returned from CreateProcess is
* duplicated from the agent and returned to the winpty client. The client is
* responsible for closing these HANDLES.
*
* On failure, the function returns FALSE, and if err is non-NULL, then *err
* is set to an error object.
*
* If the agent's CreateProcess call failed, then *create_process_error is set
* to GetLastError(), and the WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED error
* is returned.
*
* N.B.: GetProcessId works even if the process has exited. The PID is not
* recycled until the NT process object is freed.
* (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803)
*/
WINPTY_API int winpty_start_process(winpty_t *pc,
const wchar_t *appname,
const wchar_t *cmdline,
const wchar_t *cwd,
const wchar_t *env);
WINPTY_API BOOL
winpty_spawn(winpty_t *wp,
const winpty_spawn_config_t *cfg,
HANDLE *process_handle /*OPTIONAL*/,
HANDLE *thread_handle /*OPTIONAL*/,
DWORD *create_process_error /*OPTIONAL*/,
winpty_error_ptr_t *err /*OPTIONAL*/);
/*
* Returns the exit code of the process started with winpty_start_process,
* or -1 none is available.
*/
WINPTY_API int winpty_get_exit_code(winpty_t *pc);
/*
* Returns the process id of the process started with winpty_start_process,
* or -1 none is available.
*/
WINPTY_API int winpty_get_process_id(winpty_t *pc);
/*
* Returns an overlapped-mode pipe handle that can be read and written
* like a Unix terminal.
*/
WINPTY_API HANDLE winpty_get_data_pipe(winpty_t *pc);
/*****************************************************************************
* winpty agent RPC calls: everything else */
/*
* Change the size of the Windows console.
*/
WINPTY_API int winpty_set_size(winpty_t *pc, int cols, int rows);
/* Change the size of the Windows console. Returns an error code. */
WINPTY_API BOOL
winpty_set_size(winpty_t *wp, int cols, int rows,
winpty_error_ptr_t *err /*OPTIONAL*/);
/*
* Toggle the console mode. If in console mode, no terminal escape sequences are send.
*/
WINPTY_API int winpty_set_console_mode(winpty_t *pc, int mode);
/* Frees the winpty_t object and the OS resources contained in it. This
* call breaks the connection with the agent, which should then close its
* console, terminating the processes attached to it.
*
* It is a programmer error to call this function if any other threads are
* using the winpty_t object. Undefined behavior results. */
WINPTY_API void winpty_free(winpty_t *wp);
/*
* Closes the winpty.
*/
WINPTY_API void winpty_close(winpty_t *pc);
/****************************************************************************/
#ifdef __cplusplus
}

View File

@ -0,0 +1,120 @@
// 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.
#define COMPILING_WINPTY_DLL
#include "BackgroundDesktop.h"
#include <windows.h>
#include <assert.h>
#include "../shared/DebugClient.h"
#include "Util.h"
#include "WinptyException.h"
namespace libwinpty {
static std::wstring getObjectName(HANDLE object) {
// TODO: Update for error robustness -- it can assert-fail a bit too
// easily, but it's independent of everything else.
BOOL success;
DWORD lengthNeeded = 0;
GetUserObjectInformationW(object, UOI_NAME,
nullptr, 0,
&lengthNeeded);
assert(lengthNeeded % sizeof(wchar_t) == 0);
wchar_t *tmp = new wchar_t[lengthNeeded / 2];
success = GetUserObjectInformationW(object, UOI_NAME,
tmp, lengthNeeded,
nullptr);
assert(success && "GetUserObjectInformationW failed");
std::wstring ret = tmp;
delete [] tmp;
return ret;
}
// Get a non-interactive window station for the agent. Failure is mostly
// acceptable; we'll just use the normal window station and desktop. That
// apparently happens in some contexts such as SSH logins.
// TODO: review security w.r.t. windowstation and desktop.
void BackgroundDesktop::create() {
// create() should be called at most once.
auto fail = [](const char *func) {
trace("%s failed - using normal window station and desktop", func);
};
assert(!m_created && "BackgroundDesktop::create called twice");
m_created = true;
m_newStation =
CreateWindowStationW(nullptr, 0, WINSTA_ALL_ACCESS, nullptr);
if (m_newStation == nullptr) {
fail("CreateWindowStationW");
return;
}
HWINSTA originalStation = GetProcessWindowStation();
if (originalStation == nullptr) {
fail("GetProcessWindowStation");
return;
}
if (!SetProcessWindowStation(m_newStation)) {
fail("SetProcessWindowStation");
return;
}
// Record the original station so that it will be restored.
m_originalStation = originalStation;
m_newDesktop =
CreateDesktopW(L"Default", nullptr, nullptr, 0, GENERIC_ALL, nullptr);
if (m_newDesktop == nullptr) {
fail("CreateDesktopW");
return;
}
m_newDesktopName =
getObjectName(m_newStation) + L"\\" + getObjectName(m_newDesktop);
}
void BackgroundDesktop::restoreWindowStation(bool nothrow) {
if (m_originalStation != nullptr) {
if (!SetProcessWindowStation(m_originalStation)) {
trace("could not restore window station");
if (!nothrow) {
throwLastWindowsError(L"SetProcessWindowStation failed");
}
}
m_originalStation = nullptr;
}
}
BackgroundDesktop::~BackgroundDesktop() {
restoreWindowStation(true);
if (m_newDesktop != nullptr) { CloseDesktop(m_newDesktop); }
if (m_newStation != nullptr) { CloseWindowStation(m_newStation); }
}
std::wstring getDesktopFullName() {
// TODO: This function should throw windows exceptions...
// MSDN says that the handle returned by GetThreadDesktop does not need
// to be passed to CloseDesktop.
HWINSTA station = GetProcessWindowStation();
HDESK desktop = GetThreadDesktop(GetCurrentThreadId());
assert(station != nullptr && "GetProcessWindowStation returned NULL");
assert(desktop != nullptr && "GetThreadDesktop returned NULL");
return getObjectName(station) + L"\\" + getObjectName(desktop);
}
} // libwinpty namespace

View File

@ -0,0 +1,51 @@
// 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 LIBWINPTY_BACKGROUND_DESKTOP_H
#define LIBWINPTY_BACKGROUND_DESKTOP_H
#include <windows.h>
#include <string>
namespace libwinpty {
class BackgroundDesktop {
bool m_created = false;
HWINSTA m_originalStation = nullptr;
HWINSTA m_newStation = nullptr;
HDESK m_newDesktop = nullptr;
std::wstring m_newDesktopName;
public:
void create();
void restoreWindowStation(bool nothrow=false);
const std::wstring &desktopName() const { return m_newDesktopName; }
BackgroundDesktop() {}
~BackgroundDesktop();
// No copy ctor/assignment
BackgroundDesktop(const BackgroundDesktop &other) = delete;
BackgroundDesktop &operator=(const BackgroundDesktop &other) = delete;
};
std::wstring getDesktopFullName();
} // libwinpty namespace
#endif // LIBWINPTY_BACKGROUND_DESKTOP_H

130
src/libwinpty/Util.cc Executable file
View File

@ -0,0 +1,130 @@
// 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.
#define COMPILING_WINPTY_DLL
#include "Util.h"
#include <windows.h>
#include <assert.h>
#include <string.h>
#include <string>
#include "../shared/DebugClient.h"
#include "WinptyException.h"
namespace libwinpty {
void OwnedHandle::dispose(bool nothrow) {
if (m_h != nullptr && m_h != INVALID_HANDLE_VALUE) {
if (!CloseHandle(m_h)) {
trace("CloseHandle(%p) failed", m_h);
if (!nothrow) {
throwLastWindowsError(L"CloseHandle failed");
}
}
}
m_h = nullptr;
}
// This function can throw std::bad_alloc.
wchar_t *dupWStr(const wchar_t *str) {
const size_t len = wcslen(str) + 1;
wchar_t *ret = new wchar_t[len];
memcpy(ret, str, sizeof(wchar_t) * len);
return ret;
}
// This function can throw std::bad_alloc.
wchar_t *dupWStr(const std::wstring &str) {
wchar_t *ret = new wchar_t[str.size() + 1];
str.copy(ret, str.size());
ret[str.size()] = L'\0';
return ret;
}
wchar_t *dupWStrOrNull(const std::wstring &str) WINPTY_NOEXCEPT {
try {
return dupWStr(str);
} catch (const std::bad_alloc &e) {
return nullptr;
}
}
const wchar_t *cstrFromWStringOrNull(const std::wstring &str) {
try {
return str.c_str();
} catch (const std::bad_alloc &e) {
return nullptr;
}
}
void freeWStr(const wchar_t *str) {
delete [] str;
}
HMODULE getCurrentModule() {
HMODULE module;
if (!GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(getCurrentModule),
&module)) {
throwLastWindowsError(L"GetModuleHandleExW");
}
return module;
}
std::wstring getModuleFileName(HMODULE module) {
const DWORD bufsize = 4096;
wchar_t path[bufsize];
DWORD size = GetModuleFileNameW(module, path, bufsize);
if (size == 0) {
throwLastWindowsError(L"GetModuleFileNameW");
}
if (size >= bufsize) {
throwWinptyException(WINPTY_ERROR_INTERNAL_ERROR,
L"module path is unexpectedly large (at least 4K)");
}
return std::wstring(path);
}
std::wstring dirname(const std::wstring &path) {
std::wstring::size_type pos = path.find_last_of(L"\\/");
if (pos == std::wstring::npos)
return L"";
else
return path.substr(0, pos);
}
bool pathExists(const std::wstring &path) {
return GetFileAttributes(path.c_str()) != 0xFFFFFFFF;
}
OwnedHandle createEvent() {
HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (h == nullptr) {
throwLastWindowsError(L"CreateEventW failed");
}
return OwnedHandle(h);
}
} // libwinpty namespace

107
src/libwinpty/Util.h Executable file
View File

@ -0,0 +1,107 @@
// 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 LIBWINPTY_UTIL_H
#define LIBWINPTY_UTIL_H
#include <assert.h>
#include <string>
#include <utility>
#include <vector>
#include "../include/winpty.h"
#include "../shared/cxx11_noexcept.h"
namespace libwinpty {
class OwnedHandle {
HANDLE m_h;
public:
OwnedHandle() : m_h(nullptr) {}
OwnedHandle(HANDLE h) : m_h(h) {}
~OwnedHandle() { dispose(true); }
void dispose(bool nothrow=false);
HANDLE get() const { return m_h; }
HANDLE release() { HANDLE ret = m_h; m_h = nullptr; return ret; }
OwnedHandle(const OwnedHandle &other) = delete;
OwnedHandle(OwnedHandle &&other) : m_h(other.release()) {}
OwnedHandle &operator=(const OwnedHandle &other) = delete;
OwnedHandle &operator=(OwnedHandle &&other) {
dispose();
m_h = other.release();
return *this;
}
};
// Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait
// for it to complete, even after calling CancelIo on it! See
// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This
// class enforces that requirement.
class PendingIo {
HANDLE m_file;
OVERLAPPED &m_over;
bool m_finished;
public:
// The file handle and OVERLAPPED object must live as long as the PendingIo
// object.
PendingIo(HANDLE file, OVERLAPPED &over) :
m_file(file), m_over(over), m_finished(false) {}
~PendingIo() {
if (!m_finished) {
// We're not usually that interested in CancelIo's return value.
// In any case, we must not throw an exception in this dtor.
CancelIo(&m_over);
waitForCompletion();
}
}
BOOL waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT {
m_finished = true;
return GetOverlappedResult(m_file, &m_over, &actual, TRUE);
}
BOOL waitForCompletion() WINPTY_NOEXCEPT {
DWORD actual = 0;
return waitForCompletion(actual);
}
};
inline std::vector<wchar_t> modifiableWString(const std::wstring &str) {
std::vector<wchar_t> ret(str.size() + 1);
str.copy(ret.data(), str.size());
ret[str.size()] = L'\0';
return ret;
}
wchar_t *dupWStr(const std::wstring &str);
wchar_t *dupWStr(const wchar_t *str);
wchar_t *dupWStrOrNull(const std::wstring &str) WINPTY_NOEXCEPT;
const wchar_t *cstrFromWStringOrNull(const std::wstring &str);
void freeWStr(const wchar_t *str);
HMODULE getCurrentModule();
std::wstring getModuleFileName(HMODULE module);
std::wstring dirname(const std::wstring &path);
bool pathExists(const std::wstring &path);
OwnedHandle createEvent();
} // libwinpty namespace
#endif // LIBWINPTY_UTIL_H

View File

@ -0,0 +1,92 @@
// Copyright (c) 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.
#define COMPILING_WINPTY_DLL
#include "WinptyException.h"
#include <memory>
#include "Util.h"
#include "WinptyInternal.h"
namespace libwinpty {
EXTERN_ERROR(kOutOfMemory,
WINPTY_ERROR_OUT_OF_MEMORY,
L"out of memory"
);
// This function can throw std::bad_alloc.
WinptyException::WinptyException(winpty_result_t code, const std::wstring &msg) {
std::unique_ptr<winpty_error_t> error(new winpty_error_t);
error->errorIsStatic = false;
error->code = code;
error->msg = dupWStr(msg);
m_error = error.release();
}
WinptyException::WinptyException(const WinptyException &other) WINPTY_NOEXCEPT {
if (other.m_error->errorIsStatic) {
m_error = other.m_error;
} else {
try {
std::unique_ptr<winpty_error_t> error(new winpty_error_t);
error->errorIsStatic = false;
error->code = other.m_error->code;
error->msg = dupWStr(other.m_error->msg);
m_error = error.release();
} catch (const std::bad_alloc &e) {
m_error = const_cast<winpty_error_ptr_t>(&kOutOfMemory);
}
}
}
// Throw a statically-allocated winpty_error_t object.
void throwStaticError(const winpty_error_t &error) {
throw WinptyException(const_cast<winpty_error_ptr_t>(&error));
}
void throwWinptyException(winpty_result_t code, const std::wstring &msg) {
throw WinptyException(code, msg);
}
// Code size optimization -- callers don't have to make an std::wstring.
void throwWinptyException(winpty_result_t code, const wchar_t *msg) {
throw WinptyException(code, msg);
}
void throwWindowsError(const std::wstring &prefix, DWORD error) {
wchar_t msg[64];
wsprintf(msg, L": error %u", static_cast<unsigned int>(error));
throwWinptyException(WINPTY_ERROR_WINDOWS_ERROR,
prefix + msg);
}
void throwLastWindowsError(const std::wstring &prefix) {
throwWindowsError(prefix, GetLastError());
}
// Code size optimization -- callers don't have to make an std::wstring.
void throwLastWindowsError(const wchar_t *prefix) {
throwWindowsError(prefix, GetLastError());
}
} // libwinpty namespace

75
src/libwinpty/WinptyException.h Executable file
View File

@ -0,0 +1,75 @@
// Copyright (c) 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 LIBWINPTY_WINPTY_EXCEPTION_H
#define LIBWINPTY_WINPTY_EXCEPTION_H
#include <windows.h>
#include <string>
#include "../include/winpty.h"
#include "../shared/cxx11_noexcept.h"
namespace libwinpty {
#define STATIC_ERROR(name, code, msg) \
static const winpty_error_t name = { true, (code), (msg) };
#define EXTERN_ERROR(name, code, msg) \
extern const winpty_error_t name = { true, (code), (msg) };
extern const winpty_error_t kOutOfMemory;
class WinptyException {
winpty_error_ptr_t m_error;
public:
WinptyException(winpty_result_t code, const std::wstring &msg);
WinptyException(winpty_error_ptr_t error) : m_error(error) {}
~WinptyException() {
if (m_error != nullptr) {
winpty_error_free(m_error);
}
}
winpty_error_ptr_t release() WINPTY_NOEXCEPT {
winpty_error_ptr_t ret = m_error;
m_error = nullptr;
return ret;
}
WinptyException &operator=(const WinptyException &other) = delete;
WinptyException &operator=(WinptyException &&other) = delete;
WinptyException(const WinptyException &other) WINPTY_NOEXCEPT;
WinptyException(WinptyException &&other) WINPTY_NOEXCEPT
: m_error(other.release()) {}
};
void throwStaticError(const winpty_error_t &error);
void throwWinptyException(winpty_result_t code, const std::wstring &msg);
void throwWinptyException(winpty_result_t code, const wchar_t *msg);
void throwLastWindowsError(const std::wstring &prefix);
void throwLastWindowsError(const wchar_t *prefix);
} // libwinpty namespace
#endif // LIBWINPTY_WINPTY_EXCEPTION_H

66
src/libwinpty/WinptyInternal.h Executable file
View File

@ -0,0 +1,66 @@
// Copyright (c) 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 LIBWINPTY_WINPTY_INTERNAL_H
#define LIBWINPTY_WINPTY_INTERNAL_H
#include <windows.h>
#include <string>
#include "../include/winpty.h"
#include "../shared/cxx11_mutex.h"
#include "Util.h"
// The structures in this header are not intended to be accessed directly by
// client programs.
struct winpty_error_s {
bool errorIsStatic;
winpty_result_t code;
LPCWSTR msg;
};
struct winpty_config_s {
DWORD flags = 0;
int cols = 80;
int rows = 25;
DWORD timeoutMs = 30000;
};
struct winpty_s {
winpty_cxx11::mutex mutex;
libwinpty::OwnedHandle agentProcess;
libwinpty::OwnedHandle controlPipe;
DWORD agentTimeoutMs = 0;
libwinpty::OwnedHandle ioEvent;
std::wstring coninPipeName;
std::wstring conoutPipeName;
};
struct winpty_spawn_config_s {
DWORD winptyFlags = 0;
std::wstring appname;
std::wstring cmdline;
std::wstring cwd;
std::wstring env;
};
#endif // LIBWINPTY_WINPTY_INTERNAL_H

View File

@ -21,7 +21,11 @@
ALL_TARGETS += build/winpty.dll
LIBWINPTY_OBJECTS = \
build/mingw/libwinpty/BackgroundDesktop.o \
build/mingw/libwinpty/Util.o \
build/mingw/libwinpty/WinptyException.o \
build/mingw/libwinpty/winpty.o \
build/mingw/shared/Buffer.o \
build/mingw/shared/DebugClient.o
build/winpty.dll : $(LIBWINPTY_OBJECTS)

View File

@ -20,18 +20,30 @@
#define COMPILING_WINPTY_DLL
#include <winpty.h>
#include "../include/winpty.h"
#include <windows.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <string.h>
#include <memory>
#include <new>
#include <sstream>
#include "../shared/DebugClient.h"
#include <string>
#include <utility>
#include <vector>
#include "../shared/AgentMsg.h"
#include "../shared/Buffer.h"
#include "../shared/DebugClient.h"
#include "BackgroundDesktop.h"
#include "Util.h"
#include "WinptyException.h"
#include "WinptyInternal.h"
using namespace libwinpty;
// Work around a bug with mingw-gcc-g++. mingw-w64 is unaffected. See
// GitHub issue 27.
@ -39,429 +51,553 @@
#define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
#endif
// TODO: Error handling, handle out-of-memory.
#define AGENT_EXE L"winpty-agent.exe"
static volatile LONG consoleCounter;
static volatile LONG g_consoleCounter;
struct winpty_s {
winpty_s();
HANDLE controlPipe;
HANDLE dataPipe;
};
winpty_s::winpty_s() : controlPipe(NULL), dataPipe(NULL)
{
/*****************************************************************************
* Error handling -- translate C++ exceptions to an optional error object
* output and log the result. */
WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) {
return err != nullptr ? err->code
: WINPTY_ERROR_INVALID_ARGUMENT;
}
static HMODULE getCurrentModule()
{
HMODULE module;
if (!GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(getCurrentModule),
&module)) {
assert(false && "GetModuleHandleEx failed");
WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) {
return err != nullptr ? err->msg
: L"winpty_error_str argument is NULL";
}
WINPTY_API void winpty_error_free(winpty_error_ptr_t err) {
if (err != nullptr && !err->errorIsStatic) {
freeWStr(err->msg);
delete err;
}
return module;
}
static std::wstring getModuleFileName(HMODULE module)
{
const int bufsize = 4096;
wchar_t path[bufsize];
int size = GetModuleFileNameW(module, path, bufsize);
assert(size != 0 && size != bufsize);
return std::wstring(path);
STATIC_ERROR(kInvalidArgument,
WINPTY_ERROR_INVALID_ARGUMENT,
L"invalid argument"
);
STATIC_ERROR(kBadRpcPacket,
WINPTY_ERROR_INTERNAL_ERROR,
L"bad RPC packet"
);
STATIC_ERROR(kUncaughtException,
WINPTY_ERROR_INTERNAL_ERROR,
L"uncaught C++ exception"
);
static void translateException(winpty_error_ptr_t *&err) {
winpty_error_ptr_t ret = nullptr;
try {
throw;
} catch (WinptyException &e) {
ret = e.release();
} catch (const ReadBuffer::DecodeError &e) {
ret = const_cast<winpty_error_ptr_t>(&kBadRpcPacket);
} catch (const std::bad_alloc &e) {
ret = const_cast<winpty_error_ptr_t>(&kOutOfMemory);
} catch (...) {
ret = const_cast<winpty_error_ptr_t>(&kUncaughtException);
}
trace("libwinpty error: code=%d msg='%ls'", ret->code, ret->msg);
if (err != nullptr) {
*err = ret;
} else {
winpty_error_free(ret);
}
}
static std::wstring dirname(const std::wstring &path)
{
std::wstring::size_type pos = path.find_last_of(L"\\/");
if (pos == std::wstring::npos)
return L"";
else
return path.substr(0, pos);
static void throwInvalidArgument() {
throwStaticError(kInvalidArgument);
}
static bool pathExists(const std::wstring &path)
{
return GetFileAttributes(path.c_str()) != 0xFFFFFFFF;
static inline void require_arg(bool cond) {
if (!cond) {
throwInvalidArgument();
}
}
static std::wstring findAgentProgram()
{
#define API_TRY \
if (err != nullptr) { *err = nullptr; } \
try
#define API_CATCH(ret) \
catch (...) { translateException(err); return (ret); }
/*****************************************************************************
* Configuration of a new agent. */
WINPTY_API winpty_config_t *
winpty_config_new(DWORD flags, winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg((flags & WINPTY_FLAG_MASK) == flags);
std::unique_ptr<winpty_config_t> ret(new winpty_config_t);
ret->flags = flags;
return ret.release();
} API_CATCH(nullptr)
}
WINPTY_API void winpty_config_free(winpty_config_t *cfg) {
delete cfg;
}
WINPTY_API BOOL
winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg(cfg != nullptr && cols > 0 && rows > 0);
cfg->cols = cols;
cfg->rows = rows;
return TRUE;
} API_CATCH(FALSE);
}
WINPTY_API BOOL
winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg(cfg != nullptr && timeoutMs > 0);
cfg->timeoutMs = timeoutMs;
return TRUE;
} API_CATCH(FALSE)
}
/*****************************************************************************
* Start the agent. */
static std::wstring findAgentProgram() {
std::wstring progDir = dirname(getModuleFileName(getCurrentModule()));
std::wstring ret = progDir + (L"\\" AGENT_EXE);
assert(pathExists(ret));
if (!pathExists(ret)) {
throwWinptyException(
WINPTY_ERROR_AGENT_EXE_MISSING,
L"agent executable does not exist: '" + ret + L"'");
}
return ret;
}
// Call ConnectNamedPipe and block, even for an overlapped pipe. If the
// pipe is overlapped, create a temporary event for use connecting.
static bool connectNamedPipe(HANDLE handle, bool overlapped)
{
OVERLAPPED over, *pover = NULL;
if (overlapped) {
pover = &over;
memset(&over, 0, sizeof(over));
over.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
assert(over.hEvent != NULL);
static void handlePendingIo(winpty_t *wp, OVERLAPPED &over, BOOL &success,
DWORD &actual) {
if (!success && GetLastError() == ERROR_IO_PENDING) {
PendingIo io(wp->controlPipe.get(), over);
const HANDLE waitHandles[2] = { wp->ioEvent.get(),
wp->agentProcess.get() };
DWORD waitRet = WaitForMultipleObjects(
2, waitHandles, FALSE, wp->agentTimeoutMs);
// TODO: interesting edge case to test; what if the client
// disconnects after we wake up and before we call
// GetOverlappedResult? I predict either:
// - the connect succeeds
// - the connect fails with ERROR_BROKEN_PIPE
if (waitRet != WAIT_OBJECT_0) {
// The I/O is still pending. Cancel it, close the I/O event, and
// throw an exception.
if (waitRet == WAIT_OBJECT_0 + 1) {
throwWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died");
} else if (waitRet == WAIT_TIMEOUT) {
throwWinptyException(WINPTY_ERROR_AGENT_TIMEOUT,
L"agent timed out");
} else if (waitRet == WAIT_FAILED) {
throwLastWindowsError(L"WaitForMultipleObjects failed");
} else {
throwWinptyException(WINPTY_ERROR_INTERNAL_ERROR,
L"unexpected WaitForMultipleObjects return value");
}
}
success = io.waitForCompletion(actual);
}
bool success = ConnectNamedPipe(handle, pover);
if (overlapped && !success && GetLastError() == ERROR_IO_PENDING) {
DWORD actual;
success = GetOverlappedResult(handle, pover, &actual, TRUE);
}
static void handlePendingIo(winpty_t *wp, OVERLAPPED &over, BOOL &success) {
DWORD actual = 0;
handlePendingIo(wp, over, success, actual);
}
static void handleReadWriteErrors(winpty_t *wp, BOOL success,
const wchar_t *genericErrMsg) {
if (!success) {
// TODO: We failed during the write. We *probably* should permanently
// shut things down, disconnect at least the control pipe.
// TODO: Which errors, *specifically*, do we care about?
const DWORD lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA ||
lastError == ERROR_PIPE_NOT_CONNECTED) {
throwWinptyException(WINPTY_ERROR_LOST_CONNECTION,
L"lost connection to agent");
} else {
throwLastWindowsError(genericErrMsg);
}
}
if (!success && GetLastError() == ERROR_PIPE_CONNECTED)
}
// Calls ConnectNamedPipe to wait until the agent connects to the control pipe.
static void
connectControlPipe(winpty_t *wp) {
OVERLAPPED over = {};
over.hEvent = wp->ioEvent.get();
BOOL success = ConnectNamedPipe(wp->controlPipe.get(), &over);
handlePendingIo(wp, over, success);
if (!success && GetLastError() == ERROR_PIPE_CONNECTED) {
success = TRUE;
if (overlapped)
CloseHandle(over.hEvent);
return success;
}
if (!success) {
throwLastWindowsError(L"ConnectNamedPipe failed");
}
}
static void writePacket(winpty_t *pc, const WriteBuffer &packet)
{
std::string payload = packet.str();
int32_t payloadSize = payload.size();
DWORD actual;
BOOL success = WriteFile(pc->controlPipe, &payloadSize, sizeof(int32_t), &actual, NULL);
assert(success && actual == sizeof(int32_t));
success = WriteFile(pc->controlPipe, payload.c_str(), payloadSize, &actual, NULL);
assert(success && (int32_t)actual == payloadSize);
static void writeData(winpty_t *wp, const void *data, size_t amount) {
// Perform a single pipe write.
DWORD actual = 0;
OVERLAPPED over = {};
over.hEvent = wp->ioEvent.get();
BOOL success = WriteFile(wp->controlPipe.get(), data, amount,
&actual, &over);
if (!success) {
handlePendingIo(wp, over, success, actual);
handleReadWriteErrors(wp, success, L"WriteFile failed");
}
if (actual != amount) {
// TODO: We failed during the write. We *probably* should permanently
// shut things down, disconnect at least the control pipe.
throwWinptyException(WINPTY_ERROR_INTERNAL_ERROR,
L"WriteFile wrote fewer bytes than requested");
}
}
static int32_t readInt32(winpty_t *pc)
{
int32_t result;
DWORD actual;
BOOL success = ReadFile(pc->controlPipe, &result, sizeof(int32_t), &actual, NULL);
assert(success && actual == sizeof(int32_t));
return result;
static void writePacket(winpty_t *wp, WriteBuffer &packet) {
const auto &buf = packet.buf();
packet.replaceRawInt32(0, buf.size() - sizeof(int));
writeData(wp, buf.data(), buf.size());
}
static HANDLE createNamedPipe(const std::wstring &name, bool overlapped)
{
return CreateNamedPipeW(name.c_str(),
static size_t readData(winpty_t *wp, void *data, size_t amount) {
DWORD actual = 0;
OVERLAPPED over = {};
over.hEvent = wp->ioEvent.get();
BOOL success = ReadFile(wp->controlPipe.get(), data, amount,
&actual, &over);
if (!success) {
handlePendingIo(wp, over, success, actual);
handleReadWriteErrors(wp, success, L"ReadFile failed");
}
return actual;
}
static void readAll(winpty_t *wp, void *data, size_t amount) {
while (amount > 0) {
size_t chunk = readData(wp, data, amount);
data = reinterpret_cast<char*>(data) + chunk;
amount -= chunk;
}
}
static int32_t readInt32(winpty_t *wp) {
int32_t ret = 0;
readAll(wp, &ret, sizeof(ret));
return ret;
}
// Returns a reply packet's payload.
static ReadBuffer readPacket(winpty_t *wp) {
int payloadSize = readInt32(wp);
std::vector<char> bytes(payloadSize);
readAll(wp, bytes.data(), bytes.size());
return ReadBuffer(std::move(bytes), ReadBuffer::Throw);
}
static OwnedHandle createControlPipe(const std::wstring &name) {
// TODO: Set a DACL.
// TODO: Set the reject remote clients flag.
HANDLE ret = CreateNamedPipeW(name.c_str(),
/*dwOpenMode=*/
PIPE_ACCESS_DUPLEX |
FILE_FLAG_FIRST_PIPE_INSTANCE |
(overlapped ? FILE_FLAG_OVERLAPPED : 0),
FILE_FLAG_FIRST_PIPE_INSTANCE |
FILE_FLAG_OVERLAPPED,
/*dwPipeMode=*/0,
/*nMaxInstances=*/1,
/*nOutBufferSize=*/0,
/*nInBufferSize=*/0,
/*nDefaultTimeOut=*/3000,
NULL);
}
struct BackgroundDesktop {
BackgroundDesktop();
HWINSTA originalStation;
HWINSTA station;
HDESK desktop;
std::wstring desktopName;
};
BackgroundDesktop::BackgroundDesktop() :
originalStation(NULL), station(NULL), desktop(NULL)
{
}
static std::wstring getObjectName(HANDLE object)
{
BOOL success;
DWORD lengthNeeded = 0;
GetUserObjectInformationW(object, UOI_NAME,
NULL, 0,
&lengthNeeded);
assert(lengthNeeded % sizeof(wchar_t) == 0);
wchar_t *tmp = new wchar_t[lengthNeeded / 2];
success = GetUserObjectInformationW(object, UOI_NAME,
tmp, lengthNeeded,
NULL);
assert(success && "GetUserObjectInformationW failed");
std::wstring ret = tmp;
delete [] tmp;
return ret;
/*nOutBufferSize=*/8192,
/*nInBufferSize=*/256,
/*nDefaultTimeOut=*/30000,
nullptr);
if (ret == INVALID_HANDLE_VALUE) {
throwLastWindowsError(L"CreateNamedPipeW failed");
}
return OwnedHandle(ret);
}
// For debugging purposes, provide a way to keep the console on the main window
// station, visible.
static bool shouldShowConsoleWindow()
{
static bool shouldShowConsoleWindow() {
char buf[32];
return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0;
}
// Get a non-interactive window station for the agent.
// TODO: review security w.r.t. windowstation and desktop.
static BackgroundDesktop setupBackgroundDesktop()
{
BackgroundDesktop ret;
if (!shouldShowConsoleWindow()) {
const HWINSTA originalStation = GetProcessWindowStation();
ret.station = CreateWindowStationW(NULL, 0, WINSTA_ALL_ACCESS, NULL);
if (ret.station != NULL) {
ret.originalStation = originalStation;
bool success = SetProcessWindowStation(ret.station);
assert(success && "SetProcessWindowStation failed");
ret.desktop = CreateDesktopW(L"Default", NULL, NULL, 0, GENERIC_ALL, NULL);
assert(ret.originalStation != NULL);
assert(ret.station != NULL);
assert(ret.desktop != NULL);
ret.desktopName =
getObjectName(ret.station) + L"\\" + getObjectName(ret.desktop);
} else {
trace("CreateWindowStationW failed");
}
}
return ret;
}
static void restoreOriginalDesktop(const BackgroundDesktop &desktop)
{
if (desktop.station != NULL) {
SetProcessWindowStation(desktop.originalStation);
CloseDesktop(desktop.desktop);
CloseWindowStation(desktop.station);
}
}
static std::wstring getDesktopFullName()
{
// MSDN says that the handle returned by GetThreadDesktop does not need
// to be passed to CloseDesktop.
HWINSTA station = GetProcessWindowStation();
HDESK desktop = GetThreadDesktop(GetCurrentThreadId());
assert(station != NULL && "GetProcessWindowStation returned NULL");
assert(desktop != NULL && "GetThreadDesktop returned NULL");
return getObjectName(station) + L"\\" + getObjectName(desktop);
}
static void startAgentProcess(const BackgroundDesktop &desktop,
std::wstring &controlPipeName,
std::wstring &dataPipeName,
int cols, int rows)
{
bool success;
std::wstring agentProgram = findAgentProgram();
std::wstringstream agentCmdLineStream;
agentCmdLineStream << L"\"" << agentProgram << L"\" "
<< controlPipeName << " " << dataPipeName << " "
<< cols << " " << rows;
std::wstring agentCmdLine = agentCmdLineStream.str();
static OwnedHandle startAgentProcess(const std::wstring &desktopName,
const std::wstring &controlPipeName,
DWORD flags, int cols, int rows) {
std::wstring exePath = findAgentProgram();
std::wstringstream cmdlineStream;
cmdlineStream << L"\"" << exePath << L"\" "
<< controlPipeName << " "
<< flags << " " << cols << " " << rows;
std::wstring cmdline = cmdlineStream.str();
// Start the agent.
STARTUPINFOW sui;
memset(&sui, 0, sizeof(sui));
auto desktopNameM = modifiableWString(desktopName);
STARTUPINFOW sui = {};
sui.cb = sizeof(sui);
if (desktop.station != NULL) {
sui.lpDesktop = (LPWSTR)desktop.desktopName.c_str();
if (!desktopName.empty()) {
sui.lpDesktop = desktopNameM.data();
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
std::vector<wchar_t> cmdline(agentCmdLine.size() + 1);
agentCmdLine.copy(&cmdline[0], agentCmdLine.size());
cmdline[agentCmdLine.size()] = L'\0';
success = CreateProcessW(agentProgram.c_str(),
&cmdline[0],
NULL, NULL,
/*bInheritHandles=*/FALSE,
/*dwCreationFlags=*/CREATE_NEW_CONSOLE,
NULL, NULL,
&sui, &pi);
if (success) {
trace("Created agent successfully, pid=%ld, cmdline=%ls",
(long)pi.dwProcessId, agentCmdLine.c_str());
} else {
unsigned int err = GetLastError();
trace("Error creating agent, err=%#x, cmdline=%ls",
err, agentCmdLine.c_str());
fprintf(stderr, "Error %#x starting %ls\n", err, agentCmdLine.c_str());
exit(1);
if (!shouldShowConsoleWindow()) {
sui.dwFlags |= STARTF_USESHOWWINDOW;
sui.wShowWindow = SW_HIDE;
}
PROCESS_INFORMATION pi = {};
auto cmdlineM = modifiableWString(cmdline);
const bool success =
CreateProcessW(exePath.c_str(),
cmdlineM.data(),
nullptr, nullptr,
/*bInheritHandles=*/FALSE,
/*dwCreationFlags=*/CREATE_NEW_CONSOLE,
nullptr, nullptr,
&sui, &pi);
if (!success) {
const DWORD lastError = GetLastError();
std::wstringstream ss;
ss << "winpty-agent CreateProcess failed: cmdline='" << cmdline
<< "' err=0x" << std::hex << lastError;
auto errStr = ss.str();
trace("%ls", errStr.c_str());
throwWinptyException(WINPTY_ERROR_AGENT_CREATION_FAILED, errStr);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
trace("Created agent successfully, pid=%u, cmdline=%ls",
static_cast<unsigned>(pi.dwProcessId), cmdline.c_str());
return OwnedHandle(pi.hProcess);
}
WINPTY_API winpty_t *winpty_open(int cols, int rows)
{
winpty_t *pc = new winpty_t;
WINPTY_API winpty_t *
winpty_open(const winpty_config_t *cfg,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg(cfg != nullptr);
// Start pipes.
std::wstringstream pipeName;
pipeName << L"\\\\.\\pipe\\winpty-" << GetCurrentProcessId()
<< L"-" << InterlockedIncrement(&consoleCounter);
std::wstring controlPipeName = pipeName.str() + L"-control";
std::wstring dataPipeName = pipeName.str() + L"-data";
pc->controlPipe = createNamedPipe(controlPipeName, false);
if (pc->controlPipe == INVALID_HANDLE_VALUE) {
delete pc;
return NULL;
}
pc->dataPipe = createNamedPipe(dataPipeName, true);
if (pc->dataPipe == INVALID_HANDLE_VALUE) {
delete pc;
return NULL;
}
std::unique_ptr<winpty_t> wp(new winpty_t);
wp->agentTimeoutMs = cfg->timeoutMs;
wp->ioEvent = createEvent();
// Setup a background desktop for the agent.
BackgroundDesktop desktop = setupBackgroundDesktop();
// Create control server pipe.
std::wstringstream pipeNameSS;
pipeNameSS << L"\\\\.\\pipe\\winpty-control-" << GetCurrentProcessId()
<< L"-" << InterlockedIncrement(&g_consoleCounter);
const auto pipeName = pipeNameSS.str();
wp->controlPipe = createControlPipe(pipeName);
// Start the agent.
startAgentProcess(desktop, controlPipeName, dataPipeName, cols, rows);
// TODO: Frequently, I see the CreateProcess call return successfully,
// but the agent immediately dies. The following pipe connect calls then
// hang. These calls should probably timeout. Maybe this code could also
// poll the agent process handle?
// Connect the pipes.
bool success;
success = connectNamedPipe(pc->controlPipe, false);
if (!success) {
delete pc;
return NULL;
}
success = connectNamedPipe(pc->dataPipe, true);
if (!success) {
delete pc;
return NULL;
}
// Close handles to the background desktop and restore the original window
// station. This must wait until we know the agent is running -- if we
// close these handles too soon, then the desktop and windowstation will be
// destroyed before the agent can connect with them.
restoreOriginalDesktop(desktop);
// The default security descriptor for a named pipe allows anyone to connect
// to the pipe to read, but not to write. Only the "creator owner" and
// various system accounts can write to the pipe. By sending and receiving
// a dummy message on the control pipe, we should confirm that something
// trusted (i.e. the agent we just started) successfully connected and wrote
// to one of our pipes.
WriteBuffer packet;
packet.putInt(AgentMsg::Ping);
writePacket(pc, packet);
if (readInt32(pc) != 0) {
delete pc;
return NULL;
}
// TODO: On Windows Vista and forward, we could call
// GetNamedPipeClientProcessId and verify that the PID is correct. We could
// also pass the PIPE_REJECT_REMOTE_CLIENTS flag on newer OS's.
// TODO: I suppose this code is still subject to a denial-of-service attack
// from untrusted accounts making read-only connections to the pipe. It
// should probably provide a SECURITY_DESCRIPTOR for the pipe, but the last
// time I tried that (using SDDL), I couldn't get it to work (access denied
// errors).
// Aside: An obvious way to setup these handles is to open both ends of the
// pipe in the parent process and let the child inherit its handles.
// Unfortunately, the Windows API makes inheriting handles problematic.
// MSDN says that handles have to be marked inheritable, and once they are,
// they are inherited by any call to CreateProcess with
// bInheritHandles==TRUE. To avoid accidental inheritance, the library's
// clients would be obligated not to create new processes while a thread
// was calling winpty_open. Moreover, to inherit handles, MSDN seems
// to say that bInheritHandles must be TRUE[*], but I don't want to use a
// TRUE bInheritHandles, because I want to avoid leaking handles into the
// agent process, especially if the library someday allows creating the
// agent process under a different user account.
//
// [*] The way that bInheritHandles and STARTF_USESTDHANDLES work together
// is unclear in the documentation. On one hand, for STARTF_USESTDHANDLES,
// it says that bInheritHandles must be TRUE. On Vista and up, isn't
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST an acceptable alternative to
// bInheritHandles? On the other hand, KB315939 contradicts the
// STARTF_USESTDHANDLES documentation by saying, "Your pipe handles will
// still be duplicated because Windows will always duplicate the STD
// handles, even when bInheritHandles is set to FALSE." IIRC, my testing
// showed that the KB article was correct.
return pc;
}
WINPTY_API int winpty_start_process(winpty_t *pc,
const wchar_t *appname,
const wchar_t *cmdline,
const wchar_t *cwd,
const wchar_t *env)
{
WriteBuffer packet;
packet.putInt(AgentMsg::StartProcess);
packet.putWString(appname ? appname : L"");
packet.putWString(cmdline ? cmdline : L"");
packet.putWString(cwd ? cwd : L"");
std::wstring envStr;
if (env != NULL) {
const wchar_t *p = env;
while (*p != L'\0') {
p += wcslen(p) + 1;
// Create a background desktop.
// TODO: Respect WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION.
BackgroundDesktop desktop;
if (!shouldShowConsoleWindow()) {
// TODO: Also, only do this on XP and Vista.
desktop.create();
}
p++;
envStr.assign(env, p);
// Can a Win32 environment be empty? If so, does it end with one NUL or
// two? Add an extra NUL just in case it matters.
envStr.push_back(L'\0');
}
packet.putWString(envStr);
packet.putWString(getDesktopFullName());
writePacket(pc, packet);
return readInt32(pc);
// Start the agent and connect the control pipe.
wp->agentProcess = startAgentProcess(
desktop.desktopName(), pipeName, cfg->flags, cfg->cols, cfg->rows);
connectControlPipe(wp.get());
// Close handles to the background desktop and restore the original window
// station. This must wait until we know the agent is running -- if we
// close these handles too soon, then the desktop and windowstation will be
// destroyed before the agent can connect with them.
desktop.restoreWindowStation();
// Get the CONIN/CONOUT pipe names.
auto packet = readPacket(wp.get());
wp->coninPipeName = packet.getWString();
wp->conoutPipeName = packet.getWString();
packet.assertEof();
return wp.release();
} API_CATCH(nullptr)
}
WINPTY_API int winpty_get_exit_code(winpty_t *pc)
{
WriteBuffer packet;
packet.putInt(AgentMsg::GetExitCode);
writePacket(pc, packet);
return readInt32(pc);
WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) {
return wp == nullptr ? nullptr : wp->agentProcess.get();
}
WINPTY_API int winpty_get_process_id(winpty_t *pc)
{
WriteBuffer packet;
packet.putInt(AgentMsg::GetProcessId);
writePacket(pc, packet);
return readInt32(pc);
/*****************************************************************************
* I/O pipes. */
WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) {
return wp == nullptr ? nullptr : cstrFromWStringOrNull(wp->coninPipeName);
}
WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) {
return wp == nullptr ? nullptr : cstrFromWStringOrNull(wp->conoutPipeName);
}
WINPTY_API HANDLE winpty_get_data_pipe(winpty_t *pc)
{
return pc->dataPipe;
/*****************************************************************************
* winpty agent RPC call: process creation. */
WINPTY_API winpty_spawn_config_t *
winpty_spawn_config_new(DWORD winptyFlags,
LPCWSTR appname /*OPTIONAL*/,
LPCWSTR cmdline /*OPTIONAL*/,
LPCWSTR cwd /*OPTIONAL*/,
LPCWSTR env /*OPTIONAL*/,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags);
std::unique_ptr<winpty_spawn_config_t> cfg(new winpty_spawn_config_t);
cfg->winptyFlags = winptyFlags;
if (appname != nullptr) { cfg->appname = appname; }
if (cmdline != nullptr) { cfg->cmdline = cmdline; }
if (cwd != nullptr) { cfg->cwd = cwd; }
if (env != nullptr) {
const wchar_t *p = env;
while (*p != L'\0') {
// Advance over the NUL-terminated string and position 'p'
// just beyond the string-terminator.
p += wcslen(p) + 1;
}
// Advance over the block-terminator.
p++;
cfg->env.assign(env, p);
// Presumably, an empty Win32 environment would be indicated by a
// single NUL. Add an extra NUL just in case we're wrong.
cfg->env.push_back(L'\0');
}
return cfg.release();
} API_CATCH(nullptr)
}
WINPTY_API int winpty_set_size(winpty_t *pc, int cols, int rows)
{
WriteBuffer packet;
packet.putInt(AgentMsg::SetSize);
packet.putInt(cols);
packet.putInt(rows);
writePacket(pc, packet);
return readInt32(pc);
WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) {
delete cfg;
}
WINPTY_API void winpty_close(winpty_t *pc)
{
CloseHandle(pc->controlPipe);
CloseHandle(pc->dataPipe);
delete pc;
// I can't find any documentation stating that most relevant Windows handles
// are small integers, which I know them to be. If Windows HANDLEs actually
// were arbitrary addresses, then DuplicateHandle would be unusable if the
// source process were 64-bits and the caller were 32-bits. Nevertheless, the
// winpty DLL and the agent are frequently the same architecture, so prefer a
// 64-bit type for maximal robustness.
static inline HANDLE handleFromInt64(int i) {
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(i));
}
WINPTY_API int winpty_set_console_mode(winpty_t *pc, int mode)
{
WriteBuffer packet;
packet.putInt(AgentMsg::SetConsoleMode);
packet.putInt(mode);
writePacket(pc, packet);
return readInt32(pc);
WINPTY_API BOOL
winpty_spawn(winpty_t *wp,
const winpty_spawn_config_t *cfg,
HANDLE *process_handle /*OPTIONAL*/,
HANDLE *thread_handle /*OPTIONAL*/,
DWORD *create_process_error /*OPTIONAL*/,
winpty_error_ptr_t *err /*OPTIONAL*/) {
if (process_handle != nullptr) { *process_handle = nullptr; }
if (thread_handle != nullptr) { *thread_handle = nullptr; }
if (create_process_error != nullptr) { *create_process_error = 0; }
API_TRY {
require_arg(wp != nullptr && cfg != nullptr);
winpty_cxx11::lock_guard<winpty_cxx11::mutex> lock(wp->mutex);
// Send spawn request.
WriteBuffer packet;
packet.putRawInt32(0); // payload size
packet.putInt32(AgentMsg::StartProcess);
packet.putInt32(cfg->winptyFlags);
packet.putInt32(process_handle != nullptr);
packet.putInt32(thread_handle != nullptr);
packet.putWString(cfg->appname);
packet.putWString(cfg->cmdline);
packet.putWString(cfg->cwd);
packet.putWString(cfg->env);
packet.putWString(getDesktopFullName());
writePacket(wp, packet);
// Receive reply.
auto reply = readPacket(wp);
int status = reply.getInt32();
DWORD lastError = reply.getInt32();
HANDLE process = handleFromInt64(reply.getInt64());
HANDLE thread = handleFromInt64(reply.getInt64());
reply.assertEof();
// TODO: Maybe this is good enough, but there are code paths that leak
// handles...
if (process_handle != nullptr && process != nullptr) {
if (!DuplicateHandle(wp->agentProcess.get(), process,
GetCurrentProcess(),
process_handle, 0, FALSE,
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
throwLastWindowsError(L"DuplicateHandle of process handle");
}
}
if (thread_handle != nullptr && thread != nullptr) {
if (!DuplicateHandle(wp->agentProcess.get(), thread,
GetCurrentProcess(),
thread_handle, 0, FALSE,
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
throwLastWindowsError(L"DuplicateHandle of thread handle");
}
}
// TODO: error code constants... in AgentMsg.h or winpty.h?
if (status == 1) {
// TODO: include an error number
if (create_process_error != nullptr) {
*create_process_error = lastError;
}
STATIC_ERROR(kError, WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED,
L"CreateProcess failed");
throwStaticError(kError);
} else if (status > 1) {
STATIC_ERROR(kError, WINPTY_ERROR_INTERNAL_ERROR, L"spawn failed");
throwStaticError(kError);
}
return TRUE;
} API_CATCH(FALSE)
}
/*****************************************************************************
* winpty agent RPC calls: everything else */
WINPTY_API BOOL
winpty_set_size(winpty_t *wp, int cols, int rows,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
require_arg(wp != nullptr && cols > 0 && rows > 0);
winpty_cxx11::lock_guard<winpty_cxx11::mutex> lock(wp->mutex);
WriteBuffer packet;
packet.putRawInt32(0); // payload size
packet.putInt32(AgentMsg::SetSize);
packet.putInt32(cols);
packet.putInt32(rows);
writePacket(wp, packet);
readPacket(wp).assertEof();
return TRUE;
} API_CATCH(FALSE)
}
WINPTY_API void winpty_free(winpty_t *wp) {
// At least in principle, CloseHandle can fail, so this deletion can
// fail. It won't throw an exception, but maybe there's an error that
// should be propagated?
delete wp;
}

View File

@ -24,12 +24,8 @@
struct AgentMsg
{
enum Type {
Ping,
StartProcess,
SetSize,
GetExitCode,
GetProcessId,
SetConsoleMode
};
};

116
src/shared/Buffer.cc Executable file
View File

@ -0,0 +1,116 @@
// 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 "Buffer.h"
#include <assert.h>
#include <stdint.h>
#include "DebugClient.h"
// Define the READ_BUFFER_CHECK() macro. It *must* evaluate its condition,
// exactly once.
#if WINPTY_COMPILER_HAS_EXCEPTIONS
#define THROW_DECODE_ERROR() do { throw DecodeError(); } while (false)
#else
#define THROW_DECODE_ERROR() do { abort(); } while (false)
#endif
#define READ_BUFFER_CHECK(cond) \
do { \
if (!(cond)) { \
if (m_exceptMode == Throw) { \
trace("decode error: %s", #cond); \
THROW_DECODE_ERROR(); \
} else { \
trace("decode error: %s (aborting)", #cond); \
abort(); \
} \
} \
} while (false)
enum class Piece : uint8_t { Int32, Int64, WString };
void WriteBuffer::putRawData(const void *data, size_t len) {
const auto p = reinterpret_cast<const char*>(data);
m_buf.insert(m_buf.end(), p, p + len);
}
void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) {
assert(pos + len <= m_buf.size());
const auto p = reinterpret_cast<const char*>(data);
std::copy(p, p + len, m_buf.begin());
}
void WriteBuffer::putInt32(int32_t i) {
putRawValue(Piece::Int32);
putRawValue(i);
}
void WriteBuffer::putInt64(int64_t i) {
putRawValue(Piece::Int64);
putRawValue(i);
}
// len is in characters, excluding NUL, i.e. the number of wchar_t elements
void WriteBuffer::putWString(const wchar_t *str, size_t len) {
putRawValue(Piece::WString);
putRawValue(static_cast<uint64_t>(len));
putRawData(str, sizeof(wchar_t) * len);
}
void ReadBuffer::getRawData(void *data, size_t len) {
READ_BUFFER_CHECK(m_off + len <= m_buf.size());
const char *const inp = &m_buf[m_off];
std::copy(inp, inp + len, reinterpret_cast<char*>(data));
m_off += len;
}
int32_t ReadBuffer::getInt32() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int32);
return getRawValue<int32_t>();
}
int64_t ReadBuffer::getInt64() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int64);
return getRawValue<int64_t>();
}
std::wstring ReadBuffer::getWString() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::WString);
const size_t charLen = getRawValue<uint64_t>();
const size_t byteLen = charLen * sizeof(wchar_t);
READ_BUFFER_CHECK(m_off + byteLen <= m_buf.size());
// To be strictly conforming, we can't use the convenient wstring
// constructor, because the string in m_buf mightn't be aligned.
std::wstring ret;
if (charLen > 0) {
ret.resize(charLen);
const char *const inp = &m_buf[m_off];
const auto outp = reinterpret_cast<char*>(&ret[0]);
std::copy(inp, inp + byteLen, outp);
m_off += byteLen;
}
return ret;
}
void ReadBuffer::assertEof() {
READ_BUFFER_CHECK(m_off == m_buf.size());
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2011-2012 Ryan Prichard
// 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
@ -21,79 +21,102 @@
#ifndef BUFFER_H
#define BUFFER_H
#include <sstream>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>
class WriteBuffer
{
#if !defined(__GNUC__) || defined(__EXCEPTIONS)
#define WINPTY_COMPILER_HAS_EXCEPTIONS 1
#else
#define WINPTY_COMPILER_HAS_EXCEPTIONS 0
#endif
#if WINPTY_COMPILER_HAS_EXCEPTIONS
#include <exception>
#endif
class WriteBuffer {
private:
std::stringstream ss;
std::vector<char> m_buf;
public:
void putInt(int i);
void putWString(const std::wstring &str);
void putWString(const wchar_t *str);
std::string str() const;
WriteBuffer() {}
template <typename T> void putRawValue(const T &t) {
putRawData(&t, sizeof(t));
}
template <typename T> void replaceRawValue(size_t pos, const T &t) {
replaceRawData(pos, &t, sizeof(t));
}
void putRawData(const void *data, size_t len);
void putRawInt32(int32_t i) { putRawValue(i); }
void replaceRawData(size_t pos, const void *data, size_t len);
void replaceRawInt32(size_t pos, int32_t i) { replaceRawValue(pos, i); }
void putInt(int i) { return putInt32(i); }
void putInt32(int32_t i);
void putInt64(int64_t i);
void putWString(const wchar_t *str, size_t len);
void putWString(const wchar_t *str) { putWString(str, wcslen(str)); }
void putWString(const std::wstring &str) { putWString(str.data(), str.size()); }
std::vector<char> &buf() { return m_buf; }
// MSVC 2013 does not generate these automatically, so help it out.
WriteBuffer(WriteBuffer &&other) : m_buf(std::move(other.m_buf)) {}
WriteBuffer &operator=(WriteBuffer &&other) {
m_buf = std::move(other.m_buf);
return *this;
}
};
inline void WriteBuffer::putInt(int i)
{
ss.write((const char*)&i, sizeof(i));
}
inline void WriteBuffer::putWString(const std::wstring &str)
{
putInt(str.size());
ss.write((const char*)str.c_str(), sizeof(wchar_t) * str.size());
}
inline void WriteBuffer::putWString(const wchar_t *str)
{
int len = wcslen(str);
putInt(len);
ss.write((const char*)str, sizeof(wchar_t) * len);
}
inline std::string WriteBuffer::str() const
{
return ss.str();
}
class ReadBuffer
{
private:
std::stringstream ss;
class ReadBuffer {
public:
ReadBuffer(const std::string &packet);
int getInt();
#if WINPTY_COMPILER_HAS_EXCEPTIONS
class DecodeError : public std::exception {};
#endif
enum ExceptionMode { Throw, NoThrow };
private:
std::vector<char> m_buf;
size_t m_off = 0;
ExceptionMode m_exceptMode;
public:
ReadBuffer(std::vector<char> &&buf, ExceptionMode exceptMode)
: m_buf(std::move(buf)), m_exceptMode(exceptMode) {
assert(WINPTY_COMPILER_HAS_EXCEPTIONS || exceptMode == NoThrow);
}
template <typename T> T getRawValue() {
T ret = {};
getRawData(&ret, sizeof(ret));
return ret;
}
void getRawData(void *data, size_t len);
int32_t getRawInt32() { return getRawValue<int32_t>(); }
int getInt() { return getInt32(); }
int32_t getInt32();
int64_t getInt64();
std::wstring getWString();
bool eof();
void assertEof();
// MSVC 2013 does not generate these automatically, so help it out.
ReadBuffer(ReadBuffer &&other) :
m_buf(std::move(other.m_buf)), m_off(other.m_off),
m_exceptMode(other.m_exceptMode) {}
ReadBuffer &operator=(ReadBuffer &&other) {
m_buf = std::move(other.m_buf);
m_off = other.m_off;
m_exceptMode = other.m_exceptMode;
return *this;
}
};
inline ReadBuffer::ReadBuffer(const std::string &packet) : ss(packet)
{
}
inline int ReadBuffer::getInt()
{
int i;
ss.read((char*)&i, sizeof(i));
return i;
}
inline std::wstring ReadBuffer::getWString()
{
int len = getInt();
wchar_t *tmp = new wchar_t[len];
ss.read((char*)tmp, sizeof(wchar_t) * len);
std::wstring ret(tmp, len);
delete [] tmp;
return ret;
}
inline bool ReadBuffer::eof()
{
ss.peek();
return ss.eof();
}
#endif /* BUFFER_H */

58
src/shared/cxx11_mutex.h Executable file
View File

@ -0,0 +1,58 @@
// Copyright (c) 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.
// Recent 4.x MinGW and MinGW-w64 gcc compilers lack std::mutex and
// std::lock_guard. I have a 5.2.0 MinGW-w64 compiler packaged through MSYS2
// that *is* new enough, but that's one compiler against several deficient
// ones. Wrap CRITICAL_SECTION instead.
#ifndef WINPTY_CXX11_MUTEX_H
#define WINPTY_CXX11_MUTEX_H
#include <windows.h>
namespace winpty_cxx11 {
class mutex {
CRITICAL_SECTION m_mutex;
public:
mutex() { InitializeCriticalSection(&m_mutex); }
~mutex() { DeleteCriticalSection(&m_mutex); }
void lock() { EnterCriticalSection(&m_mutex); }
void unlock() { LeaveCriticalSection(&m_mutex); }
mutex(const mutex &other) = delete;
mutex &operator=(const mutex &other) = delete;
};
template <typename T>
class lock_guard {
T &m_lock;
public:
lock_guard(T &lock) : m_lock(lock) { m_lock.lock(); }
~lock_guard() { m_lock.unlock(); }
lock_guard(const lock_guard &other) = delete;
lock_guard &operator=(const lock_guard &other) = delete;
};
} // winpty_cxx11 namespace
#endif // WINPTY_CXX11_MUTEX_H

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Ryan Prichard
// 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
@ -18,32 +18,13 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#ifndef UNIX_ADAPTER_DUAL_WAKEUP_H
#define UNIX_ADAPTER_DUAL_WAKEUP_H
#ifndef WINPTY_CXX11_NOEXCEPT_H
#define WINPTY_CXX11_NOEXCEPT_H
#include "Event.h"
#include "WakeupFd.h"
#if defined(__GNUC__)
#define WINPTY_NOEXCEPT noexcept
#else
#define WINPTY_NOEXCEPT
#endif
class DualWakeup {
public:
void set() {
m_event.set();
m_wakeupfd.set();
}
void reset() {
m_event.reset();
m_wakeupfd.reset();
}
HANDLE handle() {
return m_event.handle();
}
int fd() {
return m_wakeupfd.fd();
}
private:
Event m_event;
WakeupFd m_wakeupfd;
};
#endif // UNIX_ADAPTER_DUAL_WAKEUP_H
#endif // WINPTY_CXX11_NOEXCEPT_H

View File

@ -34,8 +34,8 @@
#include "Util.h"
#include "WakeupFd.h"
InputHandler::InputHandler(HANDLE winpty, WakeupFd &completionWakeup) :
m_winpty(winpty),
InputHandler::InputHandler(HANDLE conin, WakeupFd &completionWakeup) :
m_conin(conin),
m_completionWakeup(completionWakeup),
m_threadHasBeenJoined(false),
m_shouldShutdown(0),
@ -55,7 +55,6 @@ void InputHandler::shutdown() {
}
void InputHandler::threadProc() {
Event ioEvent;
std::vector<char> buffer(4096);
fd_set readfds;
FD_ZERO(&readfds);
@ -91,30 +90,10 @@ void InputHandler::threadProc() {
break;
}
DWORD written;
OVERLAPPED over = {0};
over.hEvent = ioEvent.handle();
BOOL ret = WriteFile(m_winpty,
DWORD written = 0;
BOOL ret = WriteFile(m_conin,
&buffer[0], numRead,
&written,
&over);
if (!ret && GetLastError() == ERROR_IO_PENDING) {
const HANDLE handles[] = {
ioEvent.handle(),
m_wakeup.handle(),
};
const DWORD waitRet =
WaitForMultipleObjects(2, handles, FALSE, INFINITE);
if (waitRet == WAIT_OBJECT_0 + 1) {
trace("InputHandler: shutting down, canceling I/O");
assert(m_shouldShutdown);
CancelIo(m_winpty);
GetOverlappedResult(m_winpty, &over, &written, TRUE);
break;
}
assert(waitRet == WAIT_OBJECT_0);
ret = GetOverlappedResult(m_winpty, &over, &written, TRUE);
}
&written, NULL);
if (!ret || written != static_cast<DWORD>(numRead)) {
if (!ret && GetLastError() == ERROR_BROKEN_PIPE) {
trace("InputHandler: pipe closed: written=%u",

View File

@ -25,13 +25,12 @@
#include <pthread.h>
#include <signal.h>
#include "DualWakeup.h"
#include "WakeupFd.h"
// Connect Cygwin blocking tty STDIN_FILENO to winpty overlapped I/O.
// Connect Cygwin blocking tty STDIN_FILENO to winpty CONIN.
class InputHandler {
public:
InputHandler(HANDLE winpty, WakeupFd &completionWakeup);
InputHandler(HANDLE conin, WakeupFd &completionWakeup);
~InputHandler() { shutdown(); }
bool isComplete() { return m_threadCompleted; }
void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); }
@ -44,10 +43,10 @@ private:
}
void threadProc();
HANDLE m_winpty;
HANDLE m_conin;
pthread_t m_thread;
WakeupFd &m_completionWakeup;
DualWakeup m_wakeup;
WakeupFd m_wakeup;
bool m_threadHasBeenJoined;
volatile sig_atomic_t m_shouldShutdown;
volatile sig_atomic_t m_threadCompleted;

View File

@ -29,15 +29,13 @@
#include <vector>
#include "../shared/DebugClient.h"
#include "Event.h"
#include "Util.h"
#include "WakeupFd.h"
OutputHandler::OutputHandler(HANDLE winpty, WakeupFd &completionWakeup) :
m_winpty(winpty),
OutputHandler::OutputHandler(HANDLE conout, WakeupFd &completionWakeup) :
m_conout(conout),
m_completionWakeup(completionWakeup),
m_threadHasBeenJoined(false),
m_shouldShutdown(0),
m_threadCompleted(0)
{
assert(isatty(STDOUT_FILENO));
@ -45,7 +43,6 @@ OutputHandler::OutputHandler(HANDLE winpty, WakeupFd &completionWakeup) :
}
void OutputHandler::shutdown() {
startShutdown();
if (!m_threadHasBeenJoined) {
int ret = pthread_join(m_thread, NULL);
assert(ret == 0 && "pthread_join failed");
@ -54,41 +51,12 @@ void OutputHandler::shutdown() {
}
void OutputHandler::threadProc() {
Event ioEvent;
std::vector<char> buffer(4096);
while (true) {
// Handle shutdown
m_wakeup.reset();
if (m_shouldShutdown) {
trace("OutputHandler: shutting down");
break;
}
// Read from the pipe.
DWORD numRead;
OVERLAPPED over = {0};
over.hEvent = ioEvent.handle();
BOOL ret = ReadFile(m_winpty,
DWORD numRead = 0;
BOOL ret = ReadFile(m_conout,
&buffer[0], buffer.size(),
&numRead,
&over);
if (!ret && GetLastError() == ERROR_IO_PENDING) {
const HANDLE handles[] = {
ioEvent.handle(),
m_wakeup.handle(),
};
const DWORD waitRet =
WaitForMultipleObjects(2, handles, FALSE, INFINITE);
if (waitRet == WAIT_OBJECT_0 + 1) {
trace("OutputHandler: shutting down, canceling I/O");
assert(m_shouldShutdown);
CancelIo(m_winpty);
GetOverlappedResult(m_winpty, &over, &numRead, TRUE);
break;
}
assert(waitRet == WAIT_OBJECT_0);
ret = GetOverlappedResult(m_winpty, &over, &numRead, TRUE);
}
&numRead, NULL);
if (!ret || numRead == 0) {
if (!ret && GetLastError() == ERROR_BROKEN_PIPE) {
trace("OutputHandler: pipe closed: numRead=%u",

View File

@ -28,13 +28,12 @@
#include "Event.h"
#include "WakeupFd.h"
// Connect winpty overlapped I/O to Cygwin blocking STDOUT_FILENO.
// Connect winpty CONOUT to Cygwin blocking STDOUT_FILENO.
class OutputHandler {
public:
OutputHandler(HANDLE winpty, WakeupFd &completionWakeup);
OutputHandler(HANDLE conout, WakeupFd &completionWakeup);
~OutputHandler() { shutdown(); }
bool isComplete() { return m_threadCompleted; }
void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); }
void shutdown();
private:
@ -44,12 +43,10 @@ private:
}
void threadProc();
HANDLE m_winpty;
HANDLE m_conout;
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;
};

View File

@ -349,29 +349,55 @@ int main(int argc, char *argv[])
winsize sz;
ioctl(STDIN_FILENO, TIOCGWINSZ, &sz);
winpty_t *winpty = winpty_open(sz.ws_col, sz.ws_row);
if (winpty == NULL) {
winpty_config_t *agentCfg = winpty_config_new(0, NULL);
assert(agentCfg != NULL);
winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row, NULL);
winpty_t *wp = winpty_open(agentCfg, NULL);
if (wp == NULL) {
fprintf(stderr, "Error creating winpty.\n");
exit(1);
}
winpty_config_free(agentCfg);
const wchar_t *coninName = winpty_conin_name(wp);
const wchar_t *conoutName = winpty_conout_name(wp);
HANDLE conin =
CreateFileW(coninName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE conout =
CreateFileW(conoutName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
assert(conin != INVALID_HANDLE_VALUE);
assert(conout != INVALID_HANDLE_VALUE);
HANDLE childHandle = NULL;
{
// Start the child process under the console.
args.childArgv[0] = convertPosixPathToWin(args.childArgv[0]);
std::string cmdLine = argvToCommandLine(args.childArgv);
wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
const int ret = winpty_start_process(winpty,
NULL,
cmdLineW,
NULL,
NULL);
if (ret != 0) {
winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
NULL, cmdLineW, NULL, NULL, NULL);
assert(spawnCfg != NULL);
winpty_error_ptr_t spawnErr = NULL;
DWORD lastError = 0;
BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL,
&lastError, &spawnErr);
winpty_spawn_config_free(spawnCfg);
if (!spawnRet) {
winpty_result_t spawnCode = winpty_error_code(spawnErr);
assert(spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED);
fprintf(stderr,
"Error %#x starting %s\n",
(unsigned int)ret,
static_cast<unsigned int>(lastError),
cmdLine.c_str());
exit(1);
}
winpty_error_free(spawnErr);
delete [] cmdLineW;
}
@ -399,8 +425,8 @@ int main(int argc, char *argv[])
CSI"?1000h" CSI"?1002h" CSI"?1003h" CSI"?1015h" CSI"?1006h");
}
OutputHandler outputHandler(winpty_get_data_pipe(winpty), mainWakeup());
InputHandler inputHandler(winpty_get_data_pipe(winpty), mainWakeup());
InputHandler inputHandler(conin, mainWakeup());
OutputHandler outputHandler(conout, mainWakeup());
while (true) {
fd_set readfds;
@ -415,21 +441,26 @@ int main(int argc, char *argv[])
ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2);
if (memcmp(&sz, &sz2, sizeof(sz)) != 0) {
sz = sz2;
winpty_set_size(winpty, sz.ws_col, sz.ws_row);
winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL);
}
}
// Check for an I/O handler shutting down (possibly indicating that the
// child process has exited).
if (outputHandler.isComplete() || inputHandler.isComplete()) {
if (inputHandler.isComplete() || outputHandler.isComplete()) {
break;
}
}
outputHandler.shutdown();
inputHandler.shutdown();
// Kill the agent connection. This will kill the agent, closing the CONIN
// and CONOUT pipes on the agent pipe, prompting our I/O handler to shut
// down.
winpty_free(wp);
const int exitCode = winpty_get_exit_code(winpty);
inputHandler.shutdown();
outputHandler.shutdown();
CloseHandle(conin);
CloseHandle(conout);
if (args.mouseInput) {
// Reseting both encoding modes (1006 and 1015) is necessary, but
@ -440,7 +471,11 @@ int main(int argc, char *argv[])
}
restoreTerminalMode(mode);
winpty_close(winpty);
DWORD exitCode = 0;
if (!GetExitCodeProcess(childHandle, &exitCode)) {
exitCode = 1;
}
CloseHandle(childHandle);
return exitCode;
}

View File

@ -31,9 +31,6 @@
{
'target_name' : 'winpty-agent',
'type' : 'executable',
'include_dirs' : [
'include',
],
'libraries' : [
'-luser32',
],
@ -72,6 +69,7 @@
'agent/main.cc',
'shared/AgentMsg.h',
'shared/Buffer.h',
'shared/Buffer.cc',
'shared/DebugClient.h',
'shared/DebugClient.cc',
'shared/OsModule.h',
@ -88,20 +86,27 @@
{
'target_name' : 'winpty',
'type' : 'shared_library',
'include_dirs' : [
'include',
],
'libraries' : [
'-luser32',
],
'sources' : [
'include/winpty.h',
'libwinpty/BackgroundDesktop.h',
'libwinpty/BackgroundDesktop.cc',
'libwinpty/Util.h',
'libwinpty/Util.cc',
'libwinpty/WinptyException.h',
'libwinpty/WinptyException.cc',
'libwinpty/WinptyInternal.h',
'libwinpty/winpty.cc',
'shared/AgentMsg.h',
'shared/Buffer.h',
'shared/Buffer.cc',
'shared/DebugClient.h',
'shared/DebugClient.cc',
'shared/c99_snprintf.h',
'shared/cxx11_mutex.h',
'shared/cxx11_noexcept.h',
],
},
{