Compare commits
26 Commits
master
...
neovim-dem
Author | SHA1 | Date | |
---|---|---|---|
|
9675a911a1 | ||
|
41762f3851 | ||
|
b844635158 | ||
|
9b69f67f56 | ||
|
e3c669ae56 | ||
|
7f1045972c | ||
|
cfb5f81de3 | ||
|
6deb5e2c26 | ||
|
873733faa4 | ||
|
3da3381859 | ||
|
80d4faa72c | ||
|
834195074a | ||
|
413f17a81e | ||
|
ede3244c26 | ||
|
cf76665cfe | ||
|
ba0e3a49fd | ||
|
f2c4f6a59b | ||
|
f04774eb06 | ||
|
840847624d | ||
|
f6c6f0558e | ||
|
a532a7ce3b | ||
|
1ec380ad61 | ||
|
1fafbc2ef5 | ||
|
95be1c291f | ||
|
fe60984ce4 | ||
|
6b31bd9d8c |
72
Makefile
72
Makefile
@ -22,6 +22,7 @@
|
||||
|
||||
default : all
|
||||
|
||||
USE_PCH ?= 1
|
||||
PREFIX ?= /usr/local
|
||||
UNIX_ADAPTER_EXE ?= console.exe
|
||||
|
||||
@ -29,29 +30,67 @@ UNIX_ADAPTER_EXE ?= console.exe
|
||||
ifeq "$(wildcard config.mk)" ""
|
||||
$(error config.mk does not exist. Please run ./configure)
|
||||
endif
|
||||
|
||||
MINGW_ENABLE_CXX11_FLAG ?= -std=c++11
|
||||
|
||||
include config.mk
|
||||
|
||||
COMMON_CXXFLAGS += \
|
||||
-DWINPTY_VERSION=$$(cat VERSION.txt | tr -d '\r\n') \
|
||||
COMMON_CXXFLAGS := $(COMMON_CXXFLAGS) \
|
||||
-DWINPTY_VERSION=$(shell cat VERSION.txt | tr -d '\r\n') \
|
||||
-DWINPTY_VERSION_SUFFIX=$(VERSION_SUFFIX) \
|
||||
-DWINPTY_COMMIT_HASH=$(COMMIT_HASH) \
|
||||
-MMD -Wall \
|
||||
-DUNICODE \
|
||||
-D_UNICODE \
|
||||
-DWINVER=0x0501 \
|
||||
-D_WIN32_WINNT=0x0501
|
||||
|
||||
UNIX_CXXFLAGS += \
|
||||
UNIX_CXXFLAGS := $(UNIX_CXXFLAGS) \
|
||||
$(COMMON_CXXFLAGS)
|
||||
|
||||
MINGW_CXXFLAGS += \
|
||||
MINGW_CXXFLAGS := $(MINGW_CXXFLAGS) \
|
||||
$(COMMON_CXXFLAGS) \
|
||||
-fno-exceptions \
|
||||
-fno-rtti \
|
||||
-O2
|
||||
-O2 \
|
||||
$(MINGW_ENABLE_CXX11_FLAG)
|
||||
|
||||
MINGW_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
UNIX_LDFLAGS += $(UNIX_LDFLAGS_STATIC)
|
||||
|
||||
define def_unix_target
|
||||
build/$1/%.o : src/%.cc VERSION.txt
|
||||
@echo Compiling $$<
|
||||
@mkdir -p $$(dir $$@)
|
||||
@$$(UNIX_CXX) $$(UNIX_CXXFLAGS) $2 -I src/include -c -o $$@ $$<
|
||||
endef
|
||||
|
||||
ifeq "$(USE_PCH)" "1"
|
||||
PCH_INCLUDE := -include
|
||||
else
|
||||
PCH_INCLUDE :=
|
||||
endif
|
||||
|
||||
define def_mingw_target
|
||||
ifeq "$$(USE_PCH)" "1"
|
||||
H_$(1) := build/$1/PrecompiledHeader.h
|
||||
H_GCH_$(1) := build/$1/PrecompiledHeader.h.gch
|
||||
|
||||
$$(H_$(1)) : src/shared/PrecompiledHeader.h
|
||||
@echo Copying $$< to $$@
|
||||
@mkdir -p $$(dir $$@)
|
||||
@cp $$< $$@
|
||||
|
||||
$$(H_GCH_$(1)) : $$(H_$(1))
|
||||
@echo Compiling $$<
|
||||
@mkdir -p $$(dir $$@)
|
||||
@$$(MINGW_CXX) $$(MINGW_CXXFLAGS) $2 -c -o $$@ $$<
|
||||
endif
|
||||
|
||||
build/$1/%.o : src/%.cc VERSION.txt $$(H_GCH_$(1))
|
||||
@echo Compiling $$<
|
||||
@mkdir -p $$(dir $$@)
|
||||
@$$(MINGW_CXX) $$(MINGW_CXXFLAGS) $2 $$(PCH_INCLUDE) $$(H_$(1)) -I src/include -c -o $$@ $$<
|
||||
endef
|
||||
|
||||
include src/subdir.mk
|
||||
|
||||
all : $(ALL_TARGETS)
|
||||
@ -68,17 +107,16 @@ install : all
|
||||
clean :
|
||||
rm -fr build
|
||||
|
||||
clean-msvs :
|
||||
rm -fr src/Default
|
||||
rm -f src/*.vcxproj
|
||||
rm -f src/*.vcxproj.filters
|
||||
rm -f src/*.sln
|
||||
|
||||
distclean : clean
|
||||
rm -f config.mk
|
||||
|
||||
.PHONY : default all tests install clean distclean
|
||||
.PHONY : default all tests install clean clean-msvs distclean
|
||||
|
||||
build/mingw/%.o : src/%.cc VERSION.txt
|
||||
@echo Compiling $<
|
||||
@mkdir -p $$(dirname $@)
|
||||
@$(MINGW_CXX) $(MINGW_CXXFLAGS) -I src/include -c -o $@ $<
|
||||
|
||||
build/unix/%.o : src/%.cc VERSION.txt
|
||||
@echo Compiling $<
|
||||
@mkdir -p $$(dirname $@)
|
||||
@$(UNIX_CXX) $(UNIX_CXXFLAGS) -I src/include -c -o $@ $<
|
||||
src/%.h :
|
||||
@echo "Missing header file $@ (stale dependency file?)"
|
||||
|
29
README.rst
29
README.rst
@ -25,34 +25,35 @@ You need the following to build winpty:
|
||||
|
||||
* A Cygwin or MSYS installation
|
||||
* GNU make
|
||||
* A MinGW g++ toolchain, v4 or later, to build ``winpty.dll`` and
|
||||
``winpty-agent.exe``
|
||||
* A g++ toolchain targeting Cygwin or MSYS, v3 or later, to build
|
||||
``console.exe``
|
||||
* A MinGW g++ toolchain capable of compiling C++11 code to build ``winpty.dll``
|
||||
and ``winpty-agent.exe``
|
||||
* A g++ toolchain targeting Cygwin or MSYS to build ``console.exe``
|
||||
|
||||
Winpty requires two g++ toolchains as it is split into two parts. The
|
||||
binaries winpty.dll and winpty-agent.exe interface with the native Windows
|
||||
command prompt window so they are compiled with the native MinGW toolchain.
|
||||
The console.exe binary interfaces with the MSYS/Cygwin terminal so it is
|
||||
compiled with the MSYS/Cygwin toolchain.
|
||||
``winpty.dll`` and ``winpty-agent.exe`` binaries interface with the native
|
||||
Windows command prompt window so they are compiled with the native MinGW
|
||||
toolchain. The console.exe binary interfaces with the MSYS/Cygwin terminal so
|
||||
it is compiled with the MSYS/Cygwin toolchain.
|
||||
|
||||
MinGW appears to be split into two distributions -- MinGW (creates 32-bit
|
||||
binaries) and MinGW-w64 (creates both 32-bit and 64-bit binaries). Either
|
||||
one is acceptable, but the compiler must be v4 or later.
|
||||
one is generally acceptable.
|
||||
|
||||
Cygwin packages
|
||||
---------------
|
||||
|
||||
The default g++ compiler for Cygwin targets Cygwin itself, but Cygwin also
|
||||
packages MinGW compilers from both the MinGW and MinGW-w64 projects. As of
|
||||
this writing, the necessary packages are:
|
||||
packages MinGW-w64 compilers. As of this writing, the necessary packages are:
|
||||
|
||||
* Either ``mingw-gcc-g++``, ``mingw64-i686-gcc-g++`` or
|
||||
``mingw64-x86_64-gcc-g++``. Select the appropriate compiler for your
|
||||
CPU architecture.
|
||||
* Either ``mingw64-i686-gcc-g++`` or ``mingw64-x86_64-gcc-g++``. Select the
|
||||
appropriate compiler for your CPU architecture.
|
||||
* ``gcc-g++``
|
||||
* ``make``
|
||||
|
||||
As of this writing (2016-01-23), only the MinGW-w64 compiler is acceptable.
|
||||
The MinGW compiler (e.g. from the ``mingw-gcc-g++`` package) is no longer
|
||||
maintained and is too buggy.
|
||||
|
||||
MSYS packages
|
||||
-------------
|
||||
|
||||
|
8
configure
vendored
8
configure
vendored
@ -64,7 +64,7 @@ case $(uname -s) in
|
||||
i686)
|
||||
echo 'uname -m identifies an i686 environment.'
|
||||
UNIX_CXX=i686-pc-cygwin-g++
|
||||
MINGW_CXX="i686-w64-mingw32-g++ i686-pc-mingw32-g++"
|
||||
MINGW_CXX=i686-w64-mingw32-g++
|
||||
;;
|
||||
x86_64)
|
||||
echo 'uname -m identifies an x86_64 environment.'
|
||||
@ -148,6 +148,12 @@ echo MINGW_CXX=$MINGW_CXX >> config.mk
|
||||
|
||||
if test $IS_MSYS1 = 1; then
|
||||
echo UNIX_CXXFLAGS += -DWINPTY_TARGET_MSYS1 >> config.mk
|
||||
# The MSYS1 MinGW compiler has a bug that prevents inclusion of algorithm
|
||||
# and math.h in normal C++11 mode. The workaround is to enable the g++11
|
||||
# mode instead. The bug was fixed on 2015-07-31, but as of 2016-01-05, the
|
||||
# fix apparently hasn't been released. See
|
||||
# http://ehc.ac/p/mingw/bugs/2250/.
|
||||
echo MINGW_ENABLE_CXX11_FLAG = -std=gnu++11 >> config.mk
|
||||
fi
|
||||
|
||||
# Figure out how to embed build info (e.g. git commit) into the binary.
|
||||
|
@ -97,7 +97,7 @@ def buildTarget(target):
|
||||
os.environ["PATH"] = target["path"] + ";" + oldPath
|
||||
subprocess.check_call(["sh.exe", "configure"])
|
||||
subprocess.check_call(["make.exe", "clean"])
|
||||
buildArgs = ["make.exe", "all", "tests"]
|
||||
buildArgs = ["make.exe", "USE_PCH=0", "all", "tests"]
|
||||
if target.get("allow_parallel_make", True):
|
||||
buildArgs += ["-j8"]
|
||||
subprocess.check_call(buildArgs)
|
||||
|
@ -19,30 +19,37 @@
|
||||
// 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 <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../shared/AgentMsg.h"
|
||||
#include "../shared/Buffer.h"
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/StringBuilder.h"
|
||||
#include "../shared/WindowsSecurity.h"
|
||||
#include "../shared/WinptyAssert.h"
|
||||
#include "../shared/winpty_snprintf.h"
|
||||
#include "ConsoleFont.h"
|
||||
#include "ConsoleInput.h"
|
||||
#include "NamedPipe.h"
|
||||
#include "Terminal.h"
|
||||
#include "Win32Console.h"
|
||||
|
||||
const int SC_CONSOLE_MARK = 0xFFF2;
|
||||
const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
|
||||
|
||||
#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
using namespace winpty_shared;
|
||||
|
||||
namespace {
|
||||
|
||||
static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType)
|
||||
@ -113,12 +120,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 +154,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());
|
||||
@ -180,9 +210,10 @@ Agent::Agent(LPCWSTR controlPipeName,
|
||||
Agent::~Agent()
|
||||
{
|
||||
trace("Agent exiting...");
|
||||
m_console->postCloseMessage();
|
||||
if (m_childProcess != NULL)
|
||||
agentShutdown();
|
||||
if (m_childProcess != NULL) {
|
||||
CloseHandle(m_childProcess);
|
||||
}
|
||||
delete m_console;
|
||||
delete m_terminal;
|
||||
delete m_consoleInput;
|
||||
@ -194,20 +225,56 @@ 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)
|
||||
{
|
||||
const auto name =
|
||||
(WStringBuilder(128)
|
||||
<< L"\\\\.\\pipe\\winpty-data-"
|
||||
<< (write ? L"conout-" : L"conin-")
|
||||
<< m_genRandom.uniqueName()).str_moved();
|
||||
const DWORD openMode =
|
||||
(write ? PIPE_ACCESS_OUTBOUND : PIPE_ACCESS_INBOUND)
|
||||
| FILE_FLAG_FIRST_PIPE_INSTANCE
|
||||
| FILE_FLAG_OVERLAPPED;
|
||||
const auto sd = createPipeSecurityDescriptorOwnerFullControl();
|
||||
ASSERT(sd && "error creating data pipe SECURITY_DESCRIPTOR");
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = sd.get();
|
||||
HANDLE ret = CreateNamedPipeW(name.c_str(),
|
||||
/*dwOpenMode=*/openMode,
|
||||
/*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/(write ? 8192 : 0),
|
||||
/*nInBufferSize=*/(write ? 0 : 256),
|
||||
/*nDefaultTimeOut=*/30000,
|
||||
&sa);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -835,7 +938,7 @@ void Agent::syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN])
|
||||
// XXX: The marker text generated here could easily collide with ordinary
|
||||
// console output. Does it make sense to try to avoid the collision?
|
||||
char str[SYNC_MARKER_LEN];
|
||||
c99_snprintf(str, COUNT_OF(str), "S*Y*N*C*%08x", m_syncCounter);
|
||||
winpty_snprintf(str, "S*Y*N*C*%08x", m_syncCounter);
|
||||
for (int i = 0; i < SYNC_MARKER_LEN; ++i) {
|
||||
output[i].Char.UnicodeChar = str[i];
|
||||
output[i].Attributes = 7;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../shared/GenRandom.h"
|
||||
#include "EventLoop.h"
|
||||
#include "DsrSender.h"
|
||||
#include "Coord.h"
|
||||
@ -38,6 +39,7 @@
|
||||
class Win32Console;
|
||||
class ConsoleInput;
|
||||
class ReadBuffer;
|
||||
class WriteBuffer;
|
||||
class NamedPipe;
|
||||
struct ConsoleScreenBufferInfo;
|
||||
|
||||
@ -51,24 +53,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 +98,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;
|
||||
@ -119,6 +126,8 @@ private:
|
||||
int m_dirtyLineCount;
|
||||
|
||||
std::wstring m_currentTitle;
|
||||
|
||||
winpty_shared::GenRandom m_genRandom;
|
||||
};
|
||||
|
||||
#endif // AGENT_H
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "DebugShowInput.h"
|
||||
@ -32,12 +31,15 @@
|
||||
#include "DsrSender.h"
|
||||
#include "Win32Console.h"
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/StringBuilder.h"
|
||||
#include "../shared/UnixCtrlChars.h"
|
||||
|
||||
#ifndef MAPVK_VK_TO_VSC
|
||||
#define MAPVK_VK_TO_VSC 0
|
||||
#endif
|
||||
|
||||
using namespace winpty_shared;
|
||||
|
||||
namespace {
|
||||
|
||||
struct MouseRecord {
|
||||
@ -49,14 +51,13 @@ struct MouseRecord {
|
||||
};
|
||||
|
||||
std::string MouseRecord::toString() const {
|
||||
std::stringstream ss;
|
||||
ss << "pos=" << std::dec << coord.X << "," << coord.Y
|
||||
<< " flags=0x"
|
||||
<< std::hex << flags;
|
||||
StringBuilder sb(40);
|
||||
sb << "pos=" << coord.X << ',' << coord.Y
|
||||
<< " flags=0x" << hexOfInt(flags);
|
||||
if (release) {
|
||||
ss << " release";
|
||||
sb << " release";
|
||||
}
|
||||
return ss.str();
|
||||
return sb.str_moved();
|
||||
}
|
||||
|
||||
const unsigned int kIncompleteEscapeTimeoutMs = 1000u;
|
||||
|
@ -22,8 +22,11 @@
|
||||
#define COORD_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../shared/winpty_snprintf.h"
|
||||
|
||||
struct Coord : COORD {
|
||||
Coord()
|
||||
{
|
||||
@ -73,7 +76,12 @@ struct Coord : COORD {
|
||||
return X <= 0 || Y <= 0;
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
std::string toString() const
|
||||
{
|
||||
char ret[32];
|
||||
winpty_snprintf(ret, "(%d,%d)", X, Y);
|
||||
return std::string(ret);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // COORD_H
|
||||
|
@ -25,12 +25,13 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include "../shared/StringBuilder.h"
|
||||
#include "InputMap.h"
|
||||
|
||||
using namespace winpty_shared;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Flag {
|
||||
@ -65,7 +66,7 @@ static const Flag kMouseEventFlags[] = {
|
||||
{ MOUSE_WHEELED, "Wheel" },
|
||||
};
|
||||
|
||||
static void writeFlags(std::ostream &out, DWORD flags,
|
||||
static void writeFlags(StringBuilder &out, DWORD flags,
|
||||
const char *remainderName,
|
||||
const Flag *table, size_t tableSize,
|
||||
char pre, char sep, char post) {
|
||||
@ -75,9 +76,9 @@ static void writeFlags(std::ostream &out, DWORD flags,
|
||||
const Flag &f = table[i];
|
||||
if ((f.value & flags) == f.value) {
|
||||
if (!wroteSomething && pre != '\0') {
|
||||
out.put(pre);
|
||||
out << pre;
|
||||
} else if (wroteSomething && sep != '\0') {
|
||||
out.put(sep);
|
||||
out << sep;
|
||||
}
|
||||
out << f.text;
|
||||
wroteSomething = true;
|
||||
@ -86,23 +87,20 @@ static void writeFlags(std::ostream &out, DWORD flags,
|
||||
}
|
||||
if (remaining != 0) {
|
||||
if (!wroteSomething && pre != '\0') {
|
||||
out.put(pre);
|
||||
out << pre;
|
||||
} else if (wroteSomething && sep != '\0') {
|
||||
out.put(sep);
|
||||
out << sep;
|
||||
}
|
||||
std::ios oldState(NULL);
|
||||
oldState.copyfmt(out);
|
||||
out << std::hex << remainderName << "(0x" << remaining << ")";
|
||||
out.copyfmt(oldState);
|
||||
out << remainderName << "(0x" << hexOfInt(remaining) << ')';
|
||||
wroteSomething = true;
|
||||
}
|
||||
if (wroteSomething && post != '\0') {
|
||||
out.put(post);
|
||||
out << post;
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t n>
|
||||
static void writeFlags(std::ostream &out, DWORD flags,
|
||||
static void writeFlags(StringBuilder &out, DWORD flags,
|
||||
const char *remainderName,
|
||||
const Flag (&table)[n],
|
||||
char pre, char sep, char post) {
|
||||
@ -112,25 +110,25 @@ static void writeFlags(std::ostream &out, DWORD flags,
|
||||
} // anonymous namespace
|
||||
|
||||
std::string controlKeyStatePrefix(DWORD controlKeyState) {
|
||||
std::stringstream ss;
|
||||
writeFlags(ss, controlKeyState,
|
||||
StringBuilder sb;
|
||||
writeFlags(sb, controlKeyState,
|
||||
"keyState", kControlKeyStates, '\0', '-', '-');
|
||||
return ss.str();
|
||||
return sb.str_moved();
|
||||
}
|
||||
|
||||
std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer) {
|
||||
const uint16_t buttons = mer.dwButtonState & 0xFFFF;
|
||||
const int16_t wheel = mer.dwButtonState >> 16;
|
||||
std::stringstream ss;
|
||||
ss << std::dec << "pos=" << mer.dwMousePosition.X << ','
|
||||
StringBuilder sb;
|
||||
sb << "pos=" << mer.dwMousePosition.X << ','
|
||||
<< mer.dwMousePosition.Y;
|
||||
writeFlags(ss, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0');
|
||||
writeFlags(ss, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0');
|
||||
writeFlags(ss, buttons, "buttons", kButtonStates, ' ', ' ', '\0');
|
||||
writeFlags(sb, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0');
|
||||
writeFlags(sb, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0');
|
||||
writeFlags(sb, buttons, "buttons", kButtonStates, ' ', ' ', '\0');
|
||||
if (wheel != 0) {
|
||||
ss << " wheel=" << std::dec << wheel;
|
||||
sb << " wheel=" << wheel;
|
||||
}
|
||||
return ss.str();
|
||||
return sb.str_moved();
|
||||
}
|
||||
|
||||
void debugShowInput(bool enableMouse) {
|
||||
@ -162,7 +160,7 @@ void debugShowInput(bool enableMouse) {
|
||||
bool finished = false;
|
||||
while (!finished &&
|
||||
ReadConsoleInputW(conin, records, 32, &actual) && actual >= 1) {
|
||||
std::stringstream ss;
|
||||
StringBuilder sb;
|
||||
for (DWORD i = 0; i < actual; ++i) {
|
||||
const INPUT_RECORD &record = records[i];
|
||||
if (record.EventType == KEY_EVENT) {
|
||||
@ -172,9 +170,9 @@ void debugShowInput(bool enableMouse) {
|
||||
ker.uChar.UnicodeChar,
|
||||
static_cast<uint16_t>(ker.dwControlKeyState),
|
||||
};
|
||||
ss << "key: " << (ker.bKeyDown ? "dn" : "up")
|
||||
<< " rpt=" << std::dec << ker.wRepeatCount
|
||||
<< " scn=" << std::dec << ker.wVirtualScanCode
|
||||
sb << "key: " << (ker.bKeyDown ? "dn" : "up")
|
||||
<< " rpt=" << ker.wRepeatCount
|
||||
<< " scn=" << ker.wVirtualScanCode
|
||||
<< ' ' << key.toString() << '\n';
|
||||
if ((ker.dwControlKeyState & LEFT_CTRL_PRESSED) &&
|
||||
ker.wVirtualKeyCode == 'D') {
|
||||
@ -183,24 +181,26 @@ void debugShowInput(bool enableMouse) {
|
||||
}
|
||||
} else if (record.EventType == MOUSE_EVENT) {
|
||||
const MOUSE_EVENT_RECORD &mer = record.Event.MouseEvent;
|
||||
ss << "mouse: " << mouseEventToString(mer).c_str() << '\n';
|
||||
sb << "mouse: " << mouseEventToString(mer) << '\n';
|
||||
} else if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) {
|
||||
const WINDOW_BUFFER_SIZE_RECORD &wbsr =
|
||||
record.Event.WindowBufferSizeEvent;
|
||||
ss << "buffer-resized: dwSize=("
|
||||
<< std::dec << wbsr.dwSize.X << ','
|
||||
sb << "buffer-resized: dwSize=("
|
||||
<< wbsr.dwSize.X << ','
|
||||
<< wbsr.dwSize.Y << ")\n";
|
||||
} else if (record.EventType == MENU_EVENT) {
|
||||
const MENU_EVENT_RECORD &mer = record.Event.MenuEvent;
|
||||
ss << "menu-event: commandId=0x"
|
||||
<< std::hex << mer.dwCommandId << '\n';
|
||||
sb << "menu-event: commandId=0x"
|
||||
<< hexOfInt(mer.dwCommandId) << '\n';
|
||||
} else if (record.EventType == FOCUS_EVENT) {
|
||||
const FOCUS_EVENT_RECORD &fer = record.Event.FocusEvent;
|
||||
ss << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n';
|
||||
sb << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n';
|
||||
}
|
||||
}
|
||||
std::cout << ss.str();
|
||||
std::cout.flush();
|
||||
|
||||
const auto str = sb.str_moved();
|
||||
fwrite(str.data(), 1, str.size(), stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
SetConsoleMode(conin, origConsoleMode);
|
||||
}
|
||||
|
@ -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 == %p", 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);
|
||||
if (m_inputWorker != NULL) {
|
||||
m_inputWorker->waitForCanceledIo();
|
||||
}
|
||||
if (m_outputWorker != NULL) {
|
||||
m_outputWorker->waitForCanceledIo();
|
||||
}
|
||||
delete m_inputWorker;
|
||||
delete m_outputWorker;
|
||||
CloseHandle(m_handle);
|
||||
|
@ -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;
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "../shared/winpty_snprintf.h"
|
||||
#include "Coord.h"
|
||||
|
||||
struct SmallRect : SMALL_RECT
|
||||
@ -122,7 +123,13 @@ struct SmallRect : SMALL_RECT
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
std::string toString() const
|
||||
{
|
||||
char ret[64];
|
||||
winpty_snprintf(ret, "(x=%d,y=%d,w=%d,h=%d)",
|
||||
Left, Top, width(), height());
|
||||
return std::string(ret);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SMALLRECT_H
|
||||
|
@ -58,13 +58,6 @@ HWND Win32Console::hwnd()
|
||||
return GetConsoleWindow();
|
||||
}
|
||||
|
||||
void Win32Console::postCloseMessage()
|
||||
{
|
||||
HWND h = hwnd();
|
||||
if (h != NULL)
|
||||
PostMessage(h, WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
void Win32Console::clearLines(
|
||||
int row,
|
||||
int count,
|
||||
|
@ -51,7 +51,6 @@ public:
|
||||
HANDLE conin();
|
||||
HANDLE conout();
|
||||
HWND hwnd();
|
||||
void postCloseMessage();
|
||||
void clearLines(int row, int count, const ConsoleScreenBufferInfo &info);
|
||||
void clearAllLines(const ConsoleScreenBufferInfo &info);
|
||||
|
||||
|
@ -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,8 +72,12 @@ 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();
|
||||
|
||||
// The Agent destructor shouldn't return, but if it does, exit
|
||||
// unsuccessfully.
|
||||
return 1;
|
||||
}
|
||||
|
@ -20,26 +20,30 @@
|
||||
|
||||
ALL_TARGETS += build/winpty-agent.exe
|
||||
|
||||
$(eval $(call def_mingw_target,agent,-DWINPTY_AGENT_ASSERT))
|
||||
|
||||
AGENT_OBJECTS = \
|
||||
build/mingw/agent/Agent.o \
|
||||
build/mingw/agent/ConsoleFont.o \
|
||||
build/mingw/agent/ConsoleInput.o \
|
||||
build/mingw/agent/ConsoleLine.o \
|
||||
build/mingw/agent/Coord.o \
|
||||
build/mingw/agent/DebugShowInput.o \
|
||||
build/mingw/agent/DefaultInputMap.o \
|
||||
build/mingw/agent/EventLoop.o \
|
||||
build/mingw/agent/InputMap.o \
|
||||
build/mingw/agent/LargeConsoleRead.o \
|
||||
build/mingw/agent/NamedPipe.o \
|
||||
build/mingw/agent/SmallRect.o \
|
||||
build/mingw/agent/Terminal.o \
|
||||
build/mingw/agent/Win32Console.o \
|
||||
build/mingw/agent/main.o \
|
||||
build/mingw/shared/DebugClient.o \
|
||||
build/mingw/shared/WinptyAssert.o \
|
||||
build/mingw/shared/WinptyVersion.o \
|
||||
build/mingw/shared/winpty_wcsnlen.o
|
||||
build/agent/agent/Agent.o \
|
||||
build/agent/agent/ConsoleFont.o \
|
||||
build/agent/agent/ConsoleInput.o \
|
||||
build/agent/agent/ConsoleLine.o \
|
||||
build/agent/agent/DebugShowInput.o \
|
||||
build/agent/agent/DefaultInputMap.o \
|
||||
build/agent/agent/EventLoop.o \
|
||||
build/agent/agent/InputMap.o \
|
||||
build/agent/agent/LargeConsoleRead.o \
|
||||
build/agent/agent/NamedPipe.o \
|
||||
build/agent/agent/Terminal.o \
|
||||
build/agent/agent/Win32Console.o \
|
||||
build/agent/agent/main.o \
|
||||
build/agent/shared/Buffer.o \
|
||||
build/agent/shared/DebugClient.o \
|
||||
build/agent/shared/GenRandom.o \
|
||||
build/agent/shared/WindowsSecurity.o \
|
||||
build/agent/shared/WinptyAssert.o \
|
||||
build/agent/shared/WinptyVersion.o \
|
||||
build/agent/shared/winpty_snprintf.o \
|
||||
build/agent/shared/winpty_wcsnlen.o
|
||||
|
||||
build/winpty-agent.exe : $(AGENT_OBJECTS)
|
||||
@echo Linking $@
|
||||
|
@ -26,9 +26,19 @@
|
||||
'configurations': {
|
||||
'Release_Win32': {
|
||||
'msvs_configuration_platform': 'Win32',
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 0, # MultiThreaded (/MT)
|
||||
},
|
||||
},
|
||||
},
|
||||
'Release_x64': {
|
||||
'msvs_configuration_platform': 'x64',
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 0, # MultiThreaded (/MT)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'msvs_configuration_attributes': {
|
||||
|
@ -23,31 +23,86 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "../shared/WindowsSecurity.h"
|
||||
|
||||
const wchar_t *kPipeName = L"\\\\.\\pipe\\DebugServer";
|
||||
|
||||
using namespace winpty_shared;
|
||||
|
||||
// A message may not be larger than this size.
|
||||
const int MSG_SIZE = 4096;
|
||||
|
||||
int main() {
|
||||
static void usage(const char *program, int code) {
|
||||
printf("Usage: %s [--everyone]\n"
|
||||
"\n"
|
||||
"Creates the named pipe %ls and reads messages. Prints each\n"
|
||||
"message to stdout. By default, only the current user can send messages.\n"
|
||||
"Pass --everyone to let anyone send a message.\n"
|
||||
"\n"
|
||||
"Use the WINPTY_DEBUG environment variable to enable winpty trace output.\n"
|
||||
"(e.g. WINPTY_DEBUG=trace for the default trace output.) Set WINPTYDBG=1\n"
|
||||
"to enable trace with older winpty versions.\n",
|
||||
program, kPipeName);
|
||||
exit(code);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool everyone = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--everyone") {
|
||||
everyone = true;
|
||||
} else if (arg == "-h" || arg == "--help") {
|
||||
usage(argv[0], 0);
|
||||
} else {
|
||||
usage(argv[0], 1);
|
||||
}
|
||||
}
|
||||
|
||||
SecurityDescriptorDynamic sd;
|
||||
PSECURITY_ATTRIBUTES psa = nullptr;
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
if (everyone) {
|
||||
sd = createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
|
||||
if (!sd) {
|
||||
fprintf(stderr,
|
||||
"error: could not create security descriptor\n"
|
||||
" Use a second debugserver instance to see logged error.\n");
|
||||
exit(1);
|
||||
}
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = sd.get();
|
||||
psa = &sa;
|
||||
}
|
||||
|
||||
HANDLE serverPipe = CreateNamedPipeW(
|
||||
L"\\\\.\\pipe\\DebugServer",
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
|
||||
PIPE_UNLIMITED_INSTANCES,
|
||||
MSG_SIZE,
|
||||
MSG_SIZE,
|
||||
10 * 1000,
|
||||
NULL);
|
||||
kPipeName,
|
||||
/*dwOpenMode=*/PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
|
||||
/*dwPipeMode=*/PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |
|
||||
rejectRemoteClientsPipeFlag(),
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/MSG_SIZE,
|
||||
/*nInBufferSize=*/MSG_SIZE,
|
||||
/*nDefaultTimeOut=*/10 * 1000,
|
||||
psa);
|
||||
|
||||
if (serverPipe == INVALID_HANDLE_VALUE) {
|
||||
fprintf(stderr, "error: could not create %ls pipe: error %u\n",
|
||||
kPipeName, static_cast<unsigned>(GetLastError()));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char msgBuffer[MSG_SIZE + 1];
|
||||
|
||||
while (true) {
|
||||
if (!ConnectNamedPipe(serverPipe, NULL)) {
|
||||
fprintf(stderr, "Error: ConnectNamedPipe failed\n");
|
||||
if (!ConnectNamedPipe(serverPipe, nullptr)) {
|
||||
fprintf(stderr, "error: ConnectNamedPipe failed\n");
|
||||
fflush(stderr);
|
||||
exit(1);
|
||||
}
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(serverPipe, msgBuffer, MSG_SIZE, &bytesRead, NULL)) {
|
||||
fprintf(stderr, "Error: ReadFile on pipe failed\n");
|
||||
if (!ReadFile(serverPipe, msgBuffer, MSG_SIZE, &bytesRead, nullptr)) {
|
||||
fprintf(stderr, "error: ReadFile on pipe failed\n");
|
||||
fflush(stderr);
|
||||
DisconnectNamedPipe(serverPipe);
|
||||
continue;
|
||||
@ -57,7 +112,7 @@ int main() {
|
||||
fflush(stdout);
|
||||
|
||||
DWORD bytesWritten = 0;
|
||||
WriteFile(serverPipe, "OK", 2, &bytesWritten, NULL);
|
||||
WriteFile(serverPipe, "OK", 2, &bytesWritten, nullptr);
|
||||
DisconnectNamedPipe(serverPipe);
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,14 @@
|
||||
|
||||
ALL_TARGETS += build/winpty-debugserver.exe
|
||||
|
||||
$(eval $(call def_mingw_target,debugserver,))
|
||||
|
||||
DEBUGSERVER_OBJECTS = \
|
||||
build/mingw/debugserver/DebugServer.o
|
||||
build/debugserver/debugserver/DebugServer.o \
|
||||
build/debugserver/shared/DebugClient.o \
|
||||
build/debugserver/shared/WindowsSecurity.o \
|
||||
build/debugserver/shared/WinptyAssert.o \
|
||||
build/debugserver/shared/winpty_snprintf.o
|
||||
|
||||
build/winpty-debugserver.exe : $(DEBUGSERVER_OBJECTS)
|
||||
@echo Linking $@
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2012 Ryan Prichard
|
||||
* Copyright (c) 2011-2016 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,15 @@
|
||||
#ifndef WINPTY_H
|
||||
#define WINPTY_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "winpty_constants.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 +42,184 @@
|
||||
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);
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Configuration of a new agent. */
|
||||
|
||||
/* The winpty_config_t object is not thread-safe. */
|
||||
typedef struct winpty_config_s winpty_config_t;
|
||||
|
||||
/* Allocate a winpty_config_t value. Returns NULL on error. There are no
|
||||
* required settings -- the object may immediately be used. agentFlags is a
|
||||
* set of zero or more WINPTY_FLAG_xxx values. An unrecognized flag is an
|
||||
* error. */
|
||||
WINPTY_API winpty_config_t *
|
||||
winpty_config_new(DWORD agentFlags, 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;
|
||||
|
||||
/* winpty_spawn_config strings do not need to live as long as the config
|
||||
* object. They are copied. Returns NULL on error. spawnFlags is a set of
|
||||
* zero or more WINPTY_SPAWN_FLAG_xxx values. 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 spawnFlags,
|
||||
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
|
||||
}
|
||||
|
102
src/include/winpty_constants.h
Executable file
102
src/include/winpty_constants.h
Executable file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2016 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 WINPTY_CONSTANTS_H
|
||||
#define WINPTY_CONSTANTS_H
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* You may want to include winpty.h instead, which includes this header.
|
||||
*
|
||||
* This file is split out from winpty.h so that the agent can access the
|
||||
* winpty flags without also declaring the libwinpty APIs.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Error codes. */
|
||||
|
||||
#define WINPTY_ERROR_SUCCESS 0
|
||||
#define WINPTY_ERROR_OUT_OF_MEMORY 1
|
||||
#define WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED 2
|
||||
#define WINPTY_ERROR_LOST_CONNECTION 3
|
||||
#define WINPTY_ERROR_AGENT_EXE_MISSING 4
|
||||
#define WINPTY_ERROR_WINDOWS_ERROR 5
|
||||
#define WINPTY_ERROR_INTERNAL_ERROR 6
|
||||
#define WINPTY_ERROR_AGENT_DIED 7
|
||||
#define WINPTY_ERROR_AGENT_TIMEOUT 8
|
||||
#define WINPTY_ERROR_AGENT_CREATION_FAILED 9
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Configuration of a new agent. */
|
||||
|
||||
/* 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 agent creation flags. */
|
||||
#define WINPTY_FLAG_MASK (0 \
|
||||
| WINPTY_FLAG_PLAIN_TEXT \
|
||||
| WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \
|
||||
| WINPTY_FLAG_LEAVE_CONSOLE_OPEN_ON_EXIT \
|
||||
)
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* winpty agent RPC call: process creation. */
|
||||
|
||||
/* 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 \
|
||||
)
|
||||
|
||||
|
||||
|
||||
#endif /* WINPTY_CONSTANTS_H */
|
118
src/libwinpty/BackgroundDesktop.cc
Executable file
118
src/libwinpty/BackgroundDesktop.cc
Executable file
@ -0,0 +1,118 @@
|
||||
// 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 "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
|
@ -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,34 @@
|
||||
// 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 LIBWINPTY_BACKGROUND_DESKTOP_H
|
||||
#define LIBWINPTY_BACKGROUND_DESKTOP_H
|
||||
|
||||
#include "Event.h"
|
||||
#include "WakeupFd.h"
|
||||
#include <windows.h>
|
||||
|
||||
class DualWakeup {
|
||||
#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 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;
|
||||
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;
|
||||
};
|
||||
|
||||
#endif // UNIX_ADAPTER_DUAL_WAKEUP_H
|
||||
std::wstring getDesktopFullName();
|
||||
|
||||
} // libwinpty namespace
|
||||
|
||||
#endif // LIBWINPTY_BACKGROUND_DESKTOP_H
|
127
src/libwinpty/Util.cc
Executable file
127
src/libwinpty/Util.cc
Executable file
@ -0,0 +1,127 @@
|
||||
// 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 "Util.h"
|
||||
|
||||
#include <windows.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
|
105
src/libwinpty/Util.h
Executable file
105
src/libwinpty/Util.h
Executable file
@ -0,0 +1,105 @@
|
||||
// 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 <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
|
90
src/libwinpty/WinptyException.cc
Executable file
90
src/libwinpty/WinptyException.cc
Executable file
@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
#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
75
src/libwinpty/WinptyException.h
Executable 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
66
src/libwinpty/WinptyInternal.h
Executable 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
|
@ -20,9 +20,19 @@
|
||||
|
||||
ALL_TARGETS += build/winpty.dll
|
||||
|
||||
$(eval $(call def_mingw_target,libwinpty,-DCOMPILING_WINPTY_DLL))
|
||||
|
||||
LIBWINPTY_OBJECTS = \
|
||||
build/mingw/libwinpty/winpty.o \
|
||||
build/mingw/shared/DebugClient.o
|
||||
build/libwinpty/libwinpty/BackgroundDesktop.o \
|
||||
build/libwinpty/libwinpty/Util.o \
|
||||
build/libwinpty/libwinpty/WinptyException.o \
|
||||
build/libwinpty/libwinpty/winpty.o \
|
||||
build/libwinpty/shared/Buffer.o \
|
||||
build/libwinpty/shared/DebugClient.o \
|
||||
build/libwinpty/shared/GenRandom.o \
|
||||
build/libwinpty/shared/WindowsSecurity.o \
|
||||
build/libwinpty/shared/WinptyAssert.o \
|
||||
build/libwinpty/shared/winpty_snprintf.o
|
||||
|
||||
build/winpty.dll : $(LIBWINPTY_OBJECTS)
|
||||
@echo Linking $@
|
||||
|
@ -18,450 +18,568 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#define COMPILING_WINPTY_DLL
|
||||
#include "../include/winpty.h"
|
||||
|
||||
#include <winpty.h>
|
||||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include "../shared/DebugClient.h"
|
||||
|
||||
#include "../shared/AgentMsg.h"
|
||||
#include "../shared/Buffer.h"
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/GenRandom.h"
|
||||
#include "../shared/StringBuilder.h"
|
||||
#include "../shared/WindowsSecurity.h"
|
||||
#include "../shared/WinptyAssert.h"
|
||||
#include "BackgroundDesktop.h"
|
||||
#include "Util.h"
|
||||
#include "WinptyException.h"
|
||||
#include "WinptyInternal.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
|
||||
|
||||
// TODO: Error handling, handle out-of-memory.
|
||||
using namespace libwinpty;
|
||||
using namespace winpty_shared;
|
||||
|
||||
#define AGENT_EXE L"winpty-agent.exe"
|
||||
|
||||
static volatile LONG 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 ? WINPTY_ERROR_SUCCESS : err->code;
|
||||
}
|
||||
|
||||
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 ? L"Success" : err->msg;
|
||||
}
|
||||
|
||||
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(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=%u msg='%ls'",
|
||||
static_cast<unsigned>(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);
|
||||
#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 {
|
||||
ASSERT((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)
|
||||
}
|
||||
|
||||
static bool pathExists(const std::wstring &path)
|
||||
{
|
||||
return GetFileAttributes(path.c_str()) != 0xFFFFFFFF;
|
||||
WINPTY_API void winpty_config_free(winpty_config_t *cfg) {
|
||||
delete cfg;
|
||||
}
|
||||
|
||||
static std::wstring findAgentProgram()
|
||||
{
|
||||
WINPTY_API BOOL
|
||||
winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows,
|
||||
winpty_error_ptr_t *err /*OPTIONAL*/) {
|
||||
API_TRY {
|
||||
ASSERT(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 {
|
||||
ASSERT(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 {
|
||||
ASSERT(false &&
|
||||
"unexpected WaitForMultipleObjects return value");
|
||||
}
|
||||
bool success = ConnectNamedPipe(handle, pover);
|
||||
if (overlapped && !success && GetLastError() == ERROR_IO_PENDING) {
|
||||
DWORD actual;
|
||||
success = GetOverlappedResult(handle, pover, &actual, TRUE);
|
||||
}
|
||||
if (!success && GetLastError() == ERROR_PIPE_CONNECTED)
|
||||
success = io.waitForCompletion(actual);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
ASSERT(success);
|
||||
}
|
||||
// TODO: Can a partial write actually happen somehow?
|
||||
ASSERT(actual == amount && "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) {
|
||||
const auto sd = createPipeSecurityDescriptorOwnerFullControl();
|
||||
if (!sd) {
|
||||
throwWinptyException(WINPTY_ERROR_INTERNAL_ERROR,
|
||||
L"could not create the control pipe's SECURITY_DESCRIPTOR");
|
||||
}
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = sd.get();
|
||||
HANDLE ret = CreateNamedPipeW(name.c_str(),
|
||||
/*dwOpenMode=*/
|
||||
PIPE_ACCESS_DUPLEX |
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE |
|
||||
(overlapped ? FILE_FLAG_OVERLAPPED : 0),
|
||||
/*dwPipeMode=*/0,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
/*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
|
||||
/*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,
|
||||
&sa);
|
||||
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) {
|
||||
const std::wstring exePath = findAgentProgram();
|
||||
const std::wstring cmdline =
|
||||
(WStringBuilder(256)
|
||||
<< L"\"" << exePath << L"\" "
|
||||
<< controlPipeName << L' '
|
||||
<< flags << L' ' << cols << L' ' << rows).str_moved();
|
||||
|
||||
// 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,
|
||||
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,
|
||||
NULL, NULL,
|
||||
nullptr, nullptr,
|
||||
&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 (!success) {
|
||||
const DWORD lastError = GetLastError();
|
||||
const auto errStr =
|
||||
(WStringBuilder(256)
|
||||
<< L"winpty-agent CreateProcess failed: cmdline='" << cmdline
|
||||
<< L"' err=0x" << whexOfInt(lastError)).str_moved();
|
||||
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 {
|
||||
ASSERT(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();
|
||||
|
||||
// Create control server pipe.
|
||||
winpty_shared::GenRandom genRandom;
|
||||
const auto pipeName =
|
||||
L"\\\\.\\pipe\\winpty-control-" + genRandom.uniqueName();
|
||||
wp->controlPipe = createControlPipe(pipeName);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Setup a background desktop for the agent.
|
||||
BackgroundDesktop desktop = setupBackgroundDesktop();
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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.
|
||||
restoreOriginalDesktop(desktop);
|
||||
desktop.restoreWindowStation();
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Get the CONIN/CONOUT pipe names.
|
||||
auto packet = readPacket(wp.get());
|
||||
wp->coninPipeName = packet.getWString();
|
||||
wp->conoutPipeName = packet.getWString();
|
||||
packet.assertEof();
|
||||
|
||||
// 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;
|
||||
return wp.release();
|
||||
} API_CATCH(nullptr)
|
||||
}
|
||||
|
||||
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) {
|
||||
WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) {
|
||||
ASSERT(wp != nullptr);
|
||||
return wp->agentProcess.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* I/O pipes. */
|
||||
|
||||
WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) {
|
||||
ASSERT(wp != nullptr);
|
||||
return cstrFromWStringOrNull(wp->coninPipeName);
|
||||
}
|
||||
WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) {
|
||||
ASSERT(wp != nullptr);
|
||||
return cstrFromWStringOrNull(wp->conoutPipeName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* 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 {
|
||||
ASSERT((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++;
|
||||
envStr.assign(env, p);
|
||||
cfg->env.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');
|
||||
// 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');
|
||||
}
|
||||
packet.putWString(envStr);
|
||||
return cfg.release();
|
||||
} API_CATCH(nullptr)
|
||||
}
|
||||
|
||||
WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) {
|
||||
delete cfg;
|
||||
}
|
||||
|
||||
// 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 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 {
|
||||
ASSERT(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(pc, packet);
|
||||
return readInt32(pc);
|
||||
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_API int winpty_get_exit_code(winpty_t *pc)
|
||||
{
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* 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 {
|
||||
ASSERT(wp != nullptr && cols > 0 && rows > 0);
|
||||
winpty_cxx11::lock_guard<winpty_cxx11::mutex> lock(wp->mutex);
|
||||
WriteBuffer packet;
|
||||
packet.putInt(AgentMsg::GetExitCode);
|
||||
writePacket(pc, packet);
|
||||
return readInt32(pc);
|
||||
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 int winpty_get_process_id(winpty_t *pc)
|
||||
{
|
||||
WriteBuffer packet;
|
||||
packet.putInt(AgentMsg::GetProcessId);
|
||||
writePacket(pc, packet);
|
||||
return readInt32(pc);
|
||||
}
|
||||
|
||||
WINPTY_API HANDLE winpty_get_data_pipe(winpty_t *pc)
|
||||
{
|
||||
return pc->dataPipe;
|
||||
}
|
||||
|
||||
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_close(winpty_t *pc)
|
||||
{
|
||||
CloseHandle(pc->controlPipe);
|
||||
CloseHandle(pc->dataPipe);
|
||||
delete pc;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
@ -24,12 +24,8 @@
|
||||
struct AgentMsg
|
||||
{
|
||||
enum Type {
|
||||
Ping,
|
||||
StartProcess,
|
||||
SetSize,
|
||||
GetExitCode,
|
||||
GetProcessId,
|
||||
SetConsoleMode
|
||||
};
|
||||
};
|
||||
|
||||
|
116
src/shared/Buffer.cc
Executable file
116
src/shared/Buffer.cc
Executable 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());
|
||||
}
|
@ -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,101 @@
|
||||
#ifndef BUFFER_H
|
||||
#define BUFFER_H
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
class WriteBuffer
|
||||
{
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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();
|
||||
std::wstring getWString();
|
||||
bool eof();
|
||||
};
|
||||
#if WINPTY_COMPILER_HAS_EXCEPTIONS
|
||||
class DecodeError : public std::exception {};
|
||||
#endif
|
||||
|
||||
inline ReadBuffer::ReadBuffer(const std::string &packet) : ss(packet)
|
||||
{
|
||||
}
|
||||
enum ExceptionMode { Throw, NoThrow };
|
||||
|
||||
inline int ReadBuffer::getInt()
|
||||
{
|
||||
int i;
|
||||
ss.read((char*)&i, sizeof(i));
|
||||
return i;
|
||||
}
|
||||
private:
|
||||
std::vector<char> m_buf;
|
||||
size_t m_off = 0;
|
||||
ExceptionMode m_exceptMode;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ReadBuffer::eof()
|
||||
{
|
||||
ss.peek();
|
||||
return ss.eof();
|
||||
}
|
||||
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();
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* BUFFER_H */
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "c99_snprintf.h"
|
||||
#include "winpty_snprintf.h"
|
||||
|
||||
void *volatile g_debugConfig;
|
||||
|
||||
@ -111,8 +111,7 @@ void trace(const char *format, ...)
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
c99_vsnprintf(message, sizeof(message), format, ap);
|
||||
message[sizeof(message) - 1] = '\0';
|
||||
winpty_vsnprintf(message, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
const int currentTime = (int)(unixTimeMillis() % (100000 * 1000));
|
||||
@ -124,12 +123,13 @@ void trace(const char *format, ...)
|
||||
baseName = (baseName != NULL) ? baseName + 1 : moduleName;
|
||||
|
||||
char fullMessage[1024];
|
||||
c99_snprintf(fullMessage, sizeof(fullMessage),
|
||||
winpty_snprintf(fullMessage,
|
||||
"[%05d.%03d %s,p%04d,t%04d]: %s",
|
||||
currentTime / 1000, currentTime % 1000,
|
||||
baseName, (int)GetCurrentProcessId(), (int)GetCurrentThreadId(),
|
||||
baseName,
|
||||
static_cast<int>(GetCurrentProcessId()),
|
||||
static_cast<int>(GetCurrentThreadId()),
|
||||
message);
|
||||
fullMessage[sizeof(fullMessage) - 1] = '\0';
|
||||
|
||||
sendToDebugServer(fullMessage);
|
||||
}
|
||||
|
@ -23,6 +23,13 @@
|
||||
|
||||
bool isTracingEnabled();
|
||||
bool hasDebugFlag(const char *flag);
|
||||
|
||||
#if defined(__CYGWIN__) || defined(__MSYS__)
|
||||
void trace(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||
#elif defined(__GNUC__)
|
||||
void trace(const char *format, ...) __attribute__((format(ms_printf, 1, 2)));
|
||||
#else
|
||||
void trace(const char *format, ...);
|
||||
#endif
|
||||
|
||||
#endif // DEBUGCLIENT_H
|
||||
|
136
src/shared/GenRandom.cc
Executable file
136
src/shared/GenRandom.cc
Executable file
@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2016 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 "GenRandom.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "DebugClient.h"
|
||||
#include "StringBuilder.h"
|
||||
|
||||
namespace winpty_shared {
|
||||
|
||||
static volatile LONG g_pipeCounter;
|
||||
|
||||
GenRandom::GenRandom() : m_advapi32(L"advapi32.dll") {
|
||||
// First try to use the pseudo-documented RtlGenRandom function from
|
||||
// advapi32.dll. Creating a CryptoAPI context is slow, and RtlGenRandom
|
||||
// avoids the overhead. It's documented in this blog post[1] and on
|
||||
// MSDN[2] with a disclaimer about future breakage. This technique is
|
||||
// apparently built-in into the MSVC CRT, though, for the rand_s function,
|
||||
// so perhaps it is stable enough.
|
||||
//
|
||||
// [1] http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx
|
||||
// [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694(v=vs.85).aspx
|
||||
//
|
||||
// Both RtlGenRandom and the Crypto API functions exist in XP and up.
|
||||
m_rtlGenRandom = reinterpret_cast<RtlGenRandom_t*>(
|
||||
m_advapi32.proc("SystemFunction036"));
|
||||
// The OsModule class logs an error message if the proc is nullptr.
|
||||
if (m_rtlGenRandom != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to the crypto API.
|
||||
m_cryptProvIsValid =
|
||||
CryptAcquireContext(&m_cryptProv, nullptr, nullptr,
|
||||
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
|
||||
if (!m_cryptProvIsValid) {
|
||||
trace("GenRandom: CryptAcquireContext failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
GenRandom::~GenRandom() {
|
||||
if (m_cryptProvIsValid) {
|
||||
CryptReleaseContext(m_cryptProv, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false if the context is invalid or the generation fails.
|
||||
bool GenRandom::fillBuffer(void *buffer, size_t size) {
|
||||
memset(buffer, 0, size);
|
||||
bool success = false;
|
||||
if (m_rtlGenRandom != nullptr) {
|
||||
success = m_rtlGenRandom(buffer, size);
|
||||
if (!success) {
|
||||
trace("GenRandom: RtlGenRandom/SystemFunction036 failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
}
|
||||
} else if (m_cryptProvIsValid) {
|
||||
success = CryptGenRandom(m_cryptProv, size,
|
||||
reinterpret_cast<BYTE*>(buffer));
|
||||
if (!success) {
|
||||
trace("GenRandom: CryptGenRandom failed, size=%d, lasterror=%u",
|
||||
static_cast<int>(size),
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Returns an empty string if either of CryptAcquireContext or CryptGenRandom
|
||||
// fail.
|
||||
std::string GenRandom::randomBytes(size_t numBytes) {
|
||||
std::string ret(numBytes, '\0');
|
||||
if (!fillBuffer(&ret[0], numBytes)) {
|
||||
return std::string();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::wstring GenRandom::randomHexString(size_t numBytes) {
|
||||
const std::string bytes = randomBytes(numBytes);
|
||||
std::wstring ret(bytes.size() * 2, L'\0');
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
static const wchar_t hex[] = L"0123456789abcdef";
|
||||
ret[i * 2] = hex[static_cast<uint8_t>(bytes[i]) >> 4];
|
||||
ret[i * 2 + 1] = hex[static_cast<uint8_t>(bytes[i]) & 0xF];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Generates a unique and hard-to-guess case-insensitive string suitable for
|
||||
// use in a pipe filename or a Windows object name.
|
||||
std::wstring GenRandom::uniqueName() {
|
||||
// First include enough information to avoid collisions assuming
|
||||
// cooperative software. This code assumes that a process won't die and
|
||||
// be replaced with a recycled PID within a single GetSystemTimeAsFileTime
|
||||
// interval.
|
||||
FILETIME monotonicTime = {};
|
||||
GetSystemTimeAsFileTime(&monotonicTime);
|
||||
uint64_t monotonicTime64;
|
||||
memcpy(&monotonicTime64, &monotonicTime, sizeof(uint64_t));
|
||||
WStringBuilder sb(64);
|
||||
sb << GetCurrentProcessId()
|
||||
<< L'-' << InterlockedIncrement(&g_pipeCounter)
|
||||
<< L'-' << whexOfInt(monotonicTime64);
|
||||
// It isn't clear to me how the crypto APIs would fail. It *probably*
|
||||
// doesn't matter that much anyway? In principle, a predictable pipe name
|
||||
// is subject to a local denial-of-service attack.
|
||||
auto random = randomHexString(12);
|
||||
if (!random.empty()) {
|
||||
sb << L'-' << random;
|
||||
}
|
||||
return sb.str_moved();
|
||||
}
|
||||
|
||||
} // winpty_shared namespace
|
59
src/shared/GenRandom.h
Executable file
59
src/shared/GenRandom.h
Executable file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2016 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 WINPTY_GEN_RANDOM_H
|
||||
#define WINPTY_GEN_RANDOM_H
|
||||
|
||||
// The original MinGW requires that we include wincrypt.h. With MinGW-w64 and
|
||||
// MSVC, including windows.h is sufficient.
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "OsModule.h"
|
||||
|
||||
namespace winpty_shared {
|
||||
|
||||
class GenRandom {
|
||||
typedef BOOLEAN WINAPI RtlGenRandom_t(PVOID, ULONG);
|
||||
|
||||
OsModule m_advapi32;
|
||||
RtlGenRandom_t *m_rtlGenRandom = nullptr;
|
||||
bool m_cryptProvIsValid = false;
|
||||
HCRYPTPROV m_cryptProv = 0;
|
||||
|
||||
public:
|
||||
GenRandom();
|
||||
~GenRandom();
|
||||
bool fillBuffer(void *buffer, size_t size);
|
||||
std::string randomBytes(size_t numBytes);
|
||||
std::wstring randomHexString(size_t numBytes);
|
||||
std::wstring uniqueName();
|
||||
|
||||
// Return true if the crypto context was successfully initialized.
|
||||
bool valid() const {
|
||||
return m_rtlGenRandom != nullptr || m_cryptProvIsValid;
|
||||
}
|
||||
};
|
||||
|
||||
} // winpty_shared namespace
|
||||
|
||||
#endif // WINPTY_GEN_RANDOM_H
|
@ -21,24 +21,36 @@
|
||||
#ifndef OS_MODULE_H
|
||||
#define OS_MODULE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "DebugClient.h"
|
||||
#include "WinptyAssert.h"
|
||||
|
||||
class OsModule {
|
||||
std::wstring m_fileName;
|
||||
HMODULE m_module;
|
||||
public:
|
||||
OsModule(const wchar_t *fileName) {
|
||||
OsModule(const wchar_t *fileName) : m_fileName(fileName) {
|
||||
m_module = LoadLibraryW(fileName);
|
||||
ASSERT(m_module != NULL);
|
||||
if (m_module == nullptr) {
|
||||
trace("Could not load %ls: error %u",
|
||||
fileName, static_cast<unsigned>(GetLastError()));
|
||||
}
|
||||
}
|
||||
~OsModule() {
|
||||
if (m_module != nullptr) {
|
||||
FreeLibrary(m_module);
|
||||
}
|
||||
}
|
||||
operator bool() const { return m_module != nullptr; }
|
||||
HMODULE handle() const { return m_module; }
|
||||
FARPROC proc(const char *funcName) {
|
||||
FARPROC ret = GetProcAddress(m_module, funcName);
|
||||
FARPROC ret = nullptr;
|
||||
if (m_module != nullptr) {
|
||||
ret = GetProcAddress(m_module, funcName);
|
||||
if (ret == NULL) {
|
||||
trace("GetProcAddress: %s is missing", funcName);
|
||||
trace("GetProcAddress: %s is missing from %ls",
|
||||
funcName, m_fileName.c_str());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
33
src/agent/SmallRect.cc → src/shared/PrecompiledHeader.h
Normal file → Executable file
33
src/agent/SmallRect.cc → src/shared/PrecompiledHeader.h
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2011-2012 Ryan Prichard
|
||||
// Copyright (c) 2016 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,13 +18,26 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#include "SmallRect.h"
|
||||
#include <stdio.h>
|
||||
#ifndef WINPTY_PRECOMPILED_HEADER_H
|
||||
#define WINPTY_PRECOMPILED_HEADER_H
|
||||
|
||||
std::string SmallRect::toString() const
|
||||
{
|
||||
char ret[64];
|
||||
sprintf(ret, "(x=%d,y=%d,w=%d,h=%d)",
|
||||
Left, Top, width(), height());
|
||||
return std::string(ret);
|
||||
}
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <sddl.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#endif // WINPTY_PRECOMPILED_HEADER_H
|
198
src/shared/StringBuilder.h
Executable file
198
src/shared/StringBuilder.h
Executable file
@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2016 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.
|
||||
|
||||
// Efficient integer->string conversion and string concatenation. The
|
||||
// hexadecimal conversion may optionally have leading zeros. Other ways to
|
||||
// convert integers to strings in C++ suffer these drawbacks:
|
||||
//
|
||||
// * std::stringstream: Inefficient, even more so than stdio.
|
||||
//
|
||||
// * std::to_string: No hexadecimal output, tends to use heap allocation, not
|
||||
// supported on Cygwin.
|
||||
//
|
||||
// * stdio routines: Requires parsing a format string (inefficient). The
|
||||
// caller *must* know how large the content is for correctness. The
|
||||
// string-printf functions are extremely inconsistent on Windows. In
|
||||
// particular, 64-bit integers, wide strings, and return values are
|
||||
// problem areas.
|
||||
//
|
||||
// StringBuilderTest.cc is a standalone program that tests this header.
|
||||
|
||||
#ifndef WINPTY_STRING_BUILDER_H
|
||||
#define WINPTY_STRING_BUILDER_H
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace winpty_shared {
|
||||
|
||||
#ifdef STRING_BUILDER_TESTING
|
||||
#include <assert.h>
|
||||
#define STRING_BUILDER_CHECK(cond) assert(cond)
|
||||
#else
|
||||
#define STRING_BUILDER_CHECK(cond)
|
||||
#endif // STRING_BUILDER_TESTING
|
||||
|
||||
template <typename C, size_t sz>
|
||||
struct ValueString {
|
||||
std::array<C, sz> m_array;
|
||||
size_t m_offset;
|
||||
size_t m_size;
|
||||
|
||||
const C *c_str() const { return m_array.data() + m_offset; }
|
||||
const C *data() const { return m_array.data() + m_offset; }
|
||||
size_t size() const { return m_size; }
|
||||
std::basic_string<C> str() const {
|
||||
return std::basic_string<C>(data(), m_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Formats an integer as decimal without leading zeros.
|
||||
template <typename C, typename I>
|
||||
ValueString<C, sizeof(I) * 3 + 1 + 1> gdecOfInt(const I value) {
|
||||
typedef typename std::make_unsigned<I>::type U;
|
||||
auto unsValue = static_cast<U>(value);
|
||||
const bool isNegative = (value < 0);
|
||||
if (isNegative) {
|
||||
unsValue = -unsValue;
|
||||
}
|
||||
decltype(gdecOfInt<C, I>(value)) out;
|
||||
auto &arr = out.m_array;
|
||||
C *const endp = arr.data() + arr.size();
|
||||
C *outp = endp;
|
||||
*(--outp) = '\0';
|
||||
STRING_BUILDER_CHECK(outp >= arr.data());
|
||||
do {
|
||||
const int digit = unsValue % 10;
|
||||
unsValue /= 10;
|
||||
*(--outp) = '0' + digit;
|
||||
STRING_BUILDER_CHECK(outp >= arr.data());
|
||||
} while (unsValue != 0);
|
||||
if (isNegative) {
|
||||
*(--outp) = '-';
|
||||
STRING_BUILDER_CHECK(outp >= arr.data());
|
||||
}
|
||||
out.m_offset = outp - arr.data();
|
||||
out.m_size = endp - outp - 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename I> decltype(gdecOfInt<char, I>(0)) decOfInt(I i) {
|
||||
return gdecOfInt<char>(i);
|
||||
}
|
||||
|
||||
template <typename I> decltype(gdecOfInt<wchar_t, I>(0)) wdecOfInt(I i) {
|
||||
return gdecOfInt<wchar_t>(i);
|
||||
}
|
||||
|
||||
// Formats an integer as hexadecimal, with or without leading zeros.
|
||||
template <typename C, bool leadingZeros=false, typename I>
|
||||
ValueString<C, sizeof(I) * 2 + 1> ghexOfInt(const I value) {
|
||||
typedef typename std::make_unsigned<I>::type U;
|
||||
const auto unsValue = static_cast<U>(value);
|
||||
static const C hex[16] = {'0','1','2','3','4','5','6','7',
|
||||
'8','9','a','b','c','d','e','f'};
|
||||
decltype(ghexOfInt<C, leadingZeros, I>(value)) out;
|
||||
auto &arr = out.m_array;
|
||||
C *outp = arr.data();
|
||||
int inIndex = 0;
|
||||
int shift = sizeof(I) * 8 - 4;
|
||||
const int len = sizeof(I) * 2;
|
||||
if (!leadingZeros) {
|
||||
for (; inIndex < len - 1; ++inIndex, shift -= 4) {
|
||||
STRING_BUILDER_CHECK(shift >= 0 && shift < sizeof(unsValue) * 8);
|
||||
const int digit = (unsValue >> shift) & 0xF;
|
||||
if (digit != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; inIndex < len; ++inIndex, shift -= 4) {
|
||||
const int digit = (unsValue >> shift) & 0xF;
|
||||
*(outp++) = hex[digit];
|
||||
STRING_BUILDER_CHECK(outp <= arr.data() + arr.size());
|
||||
}
|
||||
*(outp++) = '\0';
|
||||
STRING_BUILDER_CHECK(outp <= arr.data() + arr.size());
|
||||
out.m_offset = 0;
|
||||
out.m_size = outp - arr.data() - 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <bool leadingZeros=false, typename I>
|
||||
decltype(ghexOfInt<char, leadingZeros, I>(0)) hexOfInt(I i) {
|
||||
return ghexOfInt<char, leadingZeros, I>(i);
|
||||
}
|
||||
|
||||
template <bool leadingZeros=false, typename I>
|
||||
decltype(ghexOfInt<wchar_t, leadingZeros, I>(0)) whexOfInt(I i) {
|
||||
return ghexOfInt<wchar_t, leadingZeros, I>(i);
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class GStringBuilder {
|
||||
public:
|
||||
typedef std::basic_string<C> StringType;
|
||||
|
||||
GStringBuilder() {}
|
||||
GStringBuilder(size_t capacity) {
|
||||
m_out.reserve(capacity);
|
||||
}
|
||||
|
||||
GStringBuilder &operator<<(C ch) { m_out.push_back(ch); return *this; }
|
||||
GStringBuilder &operator<<(const C *str) { m_out.append(str); return *this; }
|
||||
GStringBuilder &operator<<(const StringType &str) { m_out.append(str); return *this; }
|
||||
|
||||
template <size_t sz>
|
||||
GStringBuilder &operator<<(const ValueString<C, sz> &str) {
|
||||
m_out.append(str.data(), str.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
GStringBuilder &operator<<(short i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(unsigned short i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(int i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(unsigned int i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(long i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(unsigned long i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(long long i) { return *this << gdecOfInt<C>(i); }
|
||||
GStringBuilder &operator<<(unsigned long long i) { return *this << gdecOfInt<C>(i); }
|
||||
|
||||
GStringBuilder &operator<<(const void *p) {
|
||||
m_out.push_back(static_cast<C>('0'));
|
||||
m_out.push_back(static_cast<C>('x'));
|
||||
*this << ghexOfInt<C>(reinterpret_cast<uintptr_t>(p));
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringType str() { return m_out; }
|
||||
StringType str_moved() { return std::move(m_out); }
|
||||
const C *c_str() const { return m_out.c_str(); }
|
||||
|
||||
private:
|
||||
StringType m_out;
|
||||
};
|
||||
|
||||
typedef GStringBuilder<char> StringBuilder;
|
||||
typedef GStringBuilder<wchar_t> WStringBuilder;
|
||||
|
||||
} // namespace winpty_shared
|
||||
|
||||
#endif // WINPTY_STRING_BUILDER_H
|
116
src/shared/StringBuilderTest.cc
Executable file
116
src/shared/StringBuilderTest.cc
Executable file
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2016 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 STRING_BUILDER_TESTING
|
||||
|
||||
#include "StringBuilder.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
using namespace winpty_shared;
|
||||
|
||||
void display(const std::string &str) { fprintf(stderr, "%s", str.c_str()); }
|
||||
void display(const std::wstring &str) { fprintf(stderr, "%ls", str.c_str()); }
|
||||
|
||||
#define CHECK_EQ(x, y) \
|
||||
do { \
|
||||
const auto xval = (x); \
|
||||
const auto yval = (y); \
|
||||
if (xval != yval) { \
|
||||
fprintf(stderr, "error: %s:%d: %s != %s: ", \
|
||||
__FILE__, __LINE__, #x, #y); \
|
||||
display(xval); \
|
||||
fprintf(stderr, " != "); \
|
||||
display(yval); \
|
||||
fprintf(stderr, "\n"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
template <typename C, typename I>
|
||||
std::basic_string<C> decOfIntSS(const I value) {
|
||||
// std::to_string and std::to_wstring are missing in Cygwin as of this
|
||||
// writing (early 2016).
|
||||
std::basic_stringstream<C> ss;
|
||||
ss << +value; // We must promote char to print it as an integer.
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
template <typename C, bool leadingZeros=false, typename I>
|
||||
std::basic_string<C> hexOfIntSS(const I value) {
|
||||
typedef typename std::make_unsigned<I>::type U;
|
||||
const unsigned long long u64Value = value & static_cast<U>(~0);
|
||||
std::basic_stringstream<C> ss;
|
||||
if (leadingZeros) {
|
||||
ss << std::setfill(static_cast<C>('0')) << std::setw(sizeof(I) * 2);
|
||||
}
|
||||
ss << std::hex << u64Value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
void testValue(I value) {
|
||||
CHECK_EQ(decOfInt(value).str(), (decOfIntSS<char>(value)));
|
||||
CHECK_EQ(wdecOfInt(value).str(), (decOfIntSS<wchar_t>(value)));
|
||||
CHECK_EQ((hexOfInt<false>(value).str()), (hexOfIntSS<char, false>(value)));
|
||||
CHECK_EQ((hexOfInt<true>(value).str()), (hexOfIntSS<char, true>(value)));
|
||||
CHECK_EQ((whexOfInt<false>(value).str()), (hexOfIntSS<wchar_t, false>(value)));
|
||||
CHECK_EQ((whexOfInt<true>(value).str()), (hexOfIntSS<wchar_t, true>(value)));
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
void testType() {
|
||||
typedef typename std::make_unsigned<I>::type U;
|
||||
const U quarter = static_cast<U>(1) << (sizeof(U) * 8 - 2);
|
||||
for (unsigned quarterIndex = 0; quarterIndex < 4; ++quarterIndex) {
|
||||
for (int offset = -18; offset <= 18; ++offset) {
|
||||
const I value = quarter * quarterIndex + static_cast<U>(offset);
|
||||
testValue(value);
|
||||
}
|
||||
}
|
||||
testValue(static_cast<I>(42));
|
||||
testValue(static_cast<I>(123456));
|
||||
testValue(static_cast<I>(0xdeadfacecafebeefull));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testType<char>();
|
||||
|
||||
testType<signed char>();
|
||||
testType<signed short>();
|
||||
testType<signed int>();
|
||||
testType<signed long>();
|
||||
testType<signed long long>();
|
||||
|
||||
testType<unsigned char>();
|
||||
testType<unsigned short>();
|
||||
testType<unsigned int>();
|
||||
testType<unsigned long>();
|
||||
testType<unsigned long long>();
|
||||
|
||||
StringBuilder() << static_cast<const void*>("TEST");
|
||||
WStringBuilder() << static_cast<const void*>("TEST");
|
||||
|
||||
fprintf(stderr, "All tests completed!\n");
|
||||
}
|
400
src/shared/WindowsSecurity.cc
Executable file
400
src/shared/WindowsSecurity.cc
Executable file
@ -0,0 +1,400 @@
|
||||
// Copyright (c) 2016 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 "WindowsSecurity.h"
|
||||
|
||||
#include <sddl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "DebugClient.h"
|
||||
#include "OsModule.h"
|
||||
|
||||
namespace winpty_shared {
|
||||
|
||||
SidDynamic getOwnerSid() {
|
||||
class AutoCloseHandle {
|
||||
HANDLE m_h;
|
||||
public:
|
||||
AutoCloseHandle(HANDLE h) : m_h(h) {}
|
||||
~AutoCloseHandle() { CloseHandle(m_h); }
|
||||
};
|
||||
|
||||
struct OwnerSidImpl : public DynamicAssoc {
|
||||
std::unique_ptr<char[]> buffer;
|
||||
};
|
||||
|
||||
HANDLE token = nullptr;
|
||||
BOOL success;
|
||||
success = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token);
|
||||
if (!success) {
|
||||
trace("error: getOwnerSid: OpenProcessToken failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return SidDynamic();
|
||||
}
|
||||
AutoCloseHandle ac(token);
|
||||
if (token == nullptr) {
|
||||
trace("error: getOwnerSid: OpenProcessToken succeeded, "
|
||||
"but token is NULL");
|
||||
return SidDynamic();
|
||||
}
|
||||
DWORD actual = 0;
|
||||
success = GetTokenInformation(token, TokenOwner,
|
||||
nullptr, 0, &actual);
|
||||
if (success || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
trace("error: getOwnerSid: expected ERROR_INSUFFICIENT_BUFFER");
|
||||
return SidDynamic();
|
||||
}
|
||||
std::unique_ptr<OwnerSidImpl> assoc(new OwnerSidImpl);
|
||||
assoc->buffer = std::unique_ptr<char[]>(new char[actual]);
|
||||
success = GetTokenInformation(token, TokenOwner,
|
||||
assoc->buffer.get(), actual, &actual);
|
||||
if (!success) {
|
||||
trace("error: getOwnerSid: GetTokenInformation failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return SidDynamic();
|
||||
}
|
||||
TOKEN_OWNER tmp;
|
||||
memcpy(&tmp, assoc->buffer.get(), sizeof(tmp));
|
||||
return SidDynamic(tmp.Owner, std::move(assoc));
|
||||
}
|
||||
|
||||
SidDefault wellKnownSid(const char *debuggingName,
|
||||
SID_IDENTIFIER_AUTHORITY authority,
|
||||
BYTE authorityCount,
|
||||
DWORD subAuthority0/*=0*/,
|
||||
DWORD subAuthority1/*=0*/) {
|
||||
PSID psid = nullptr;
|
||||
if (!AllocateAndInitializeSid(&authority, authorityCount,
|
||||
subAuthority0,
|
||||
subAuthority1,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&psid)) {
|
||||
trace("wellKnownSid: error getting %s SID: %u",
|
||||
debuggingName,
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return SidDefault();
|
||||
}
|
||||
return SidDefault(psid);
|
||||
}
|
||||
|
||||
SidDefault builtinAdminsSid() {
|
||||
// S-1-5-32-544
|
||||
SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
|
||||
return wellKnownSid("BUILTIN\\Administrators group",
|
||||
authority, 2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID, // 32
|
||||
DOMAIN_ALIAS_RID_ADMINS); // 544
|
||||
}
|
||||
|
||||
SidDefault localSystemSid() {
|
||||
// S-1-5-18
|
||||
SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
|
||||
return wellKnownSid("LocalSystem account",
|
||||
authority, 1,
|
||||
SECURITY_LOCAL_SYSTEM_RID); // 18
|
||||
}
|
||||
|
||||
SidDefault everyoneSid() {
|
||||
// S-1-1-0
|
||||
SID_IDENTIFIER_AUTHORITY authority = { SECURITY_WORLD_SID_AUTHORITY };
|
||||
return wellKnownSid("Everyone account",
|
||||
authority, 1,
|
||||
SECURITY_WORLD_RID); // 0
|
||||
}
|
||||
|
||||
static SecurityDescriptorLocal finishSecurityDescriptor(
|
||||
size_t daclEntryCount,
|
||||
EXPLICIT_ACCESSW *daclEntries,
|
||||
AclLocal &outAcl) {
|
||||
{
|
||||
PACL aclRaw = nullptr;
|
||||
DWORD aclError =
|
||||
SetEntriesInAcl(daclEntryCount,
|
||||
daclEntries,
|
||||
nullptr, &aclRaw);
|
||||
if (aclError != ERROR_SUCCESS) {
|
||||
trace("error: SetEntriesInAcl failed: %u",
|
||||
static_cast<unsigned>(aclError));
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
outAcl = AclLocal(aclRaw);
|
||||
}
|
||||
|
||||
SecurityDescriptorLocal sdLocal(
|
||||
reinterpret_cast<PSECURITY_DESCRIPTOR>(
|
||||
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)));
|
||||
if (!sdLocal) {
|
||||
trace("error: LocalAlloc failed");
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
PSECURITY_DESCRIPTOR sdRaw = sdLocal.get();
|
||||
|
||||
if (!InitializeSecurityDescriptor(sdRaw, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
trace("error: InitializeSecurityDescriptor failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
if (!SetSecurityDescriptorDacl(sdRaw, TRUE, outAcl.get(), FALSE)) {
|
||||
trace("error: SetSecurityDescriptorDacl failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
|
||||
return sdLocal;
|
||||
}
|
||||
|
||||
// Create a security descriptor that grants full control to the local system
|
||||
// account, built-in administrators, and the owner. Returns NULL on failure.
|
||||
SecurityDescriptorDynamic
|
||||
createPipeSecurityDescriptorOwnerFullControl() {
|
||||
|
||||
struct Assoc : public DynamicAssoc {
|
||||
SidDynamic owner;
|
||||
SidDefault builtinAdmins;
|
||||
SidDefault localSystem;
|
||||
std::array<EXPLICIT_ACCESSW, 3> daclEntries;
|
||||
AclLocal dacl;
|
||||
SecurityDescriptorLocal value;
|
||||
};
|
||||
|
||||
std::unique_ptr<Assoc> assoc(new Assoc);
|
||||
|
||||
if (!(assoc->owner = getOwnerSid()) ||
|
||||
!(assoc->builtinAdmins = builtinAdminsSid()) ||
|
||||
!(assoc->localSystem = localSystemSid())) {
|
||||
return SecurityDescriptorDynamic();
|
||||
}
|
||||
|
||||
for (auto &ea : assoc->daclEntries) {
|
||||
ea.grfAccessPermissions = GENERIC_ALL;
|
||||
ea.grfAccessMode = SET_ACCESS;
|
||||
ea.grfInheritance = NO_INHERITANCE;
|
||||
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
}
|
||||
assoc->daclEntries[0].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->localSystem.get());
|
||||
assoc->daclEntries[1].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->builtinAdmins.get());
|
||||
assoc->daclEntries[2].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->owner.get());
|
||||
|
||||
assoc->value = finishSecurityDescriptor(
|
||||
assoc->daclEntries.size(),
|
||||
assoc->daclEntries.data(),
|
||||
assoc->dacl);
|
||||
if (!assoc->value) {
|
||||
return SecurityDescriptorDynamic();
|
||||
}
|
||||
|
||||
const PSECURITY_DESCRIPTOR ret = assoc->value.get();
|
||||
return SecurityDescriptorDynamic(ret, std::move(assoc));
|
||||
}
|
||||
|
||||
SecurityDescriptorDynamic
|
||||
createPipeSecurityDescriptorOwnerFullControlEveryoneWrite() {
|
||||
|
||||
struct Assoc : public DynamicAssoc {
|
||||
SidDynamic owner;
|
||||
SidDefault builtinAdmins;
|
||||
SidDefault localSystem;
|
||||
SidDefault everyone;
|
||||
std::array<EXPLICIT_ACCESSW, 4> daclEntries;
|
||||
AclLocal dacl;
|
||||
SecurityDescriptorLocal value;
|
||||
};
|
||||
|
||||
std::unique_ptr<Assoc> assoc(new Assoc);
|
||||
|
||||
if (!(assoc->owner = getOwnerSid()) ||
|
||||
!(assoc->builtinAdmins = builtinAdminsSid()) ||
|
||||
!(assoc->localSystem = localSystemSid()) ||
|
||||
!(assoc->everyone = everyoneSid())) {
|
||||
return SecurityDescriptorDynamic();
|
||||
}
|
||||
|
||||
for (auto &ea : assoc->daclEntries) {
|
||||
ea.grfAccessPermissions = GENERIC_ALL;
|
||||
ea.grfAccessMode = SET_ACCESS;
|
||||
ea.grfInheritance = NO_INHERITANCE;
|
||||
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
}
|
||||
assoc->daclEntries[0].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->localSystem.get());
|
||||
assoc->daclEntries[1].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->builtinAdmins.get());
|
||||
assoc->daclEntries[2].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->owner.get());
|
||||
assoc->daclEntries[3].Trustee.ptstrName =
|
||||
reinterpret_cast<LPWSTR>(assoc->everyone.get());
|
||||
// Avoid using FILE_GENERIC_WRITE because it includes FILE_APPEND_DATA,
|
||||
// which is equal to FILE_CREATE_PIPE_INSTANCE. Instead, include all the
|
||||
// flags that comprise FILE_GENERIC_WRITE, except for the one.
|
||||
assoc->daclEntries[3].grfAccessPermissions =
|
||||
FILE_GENERIC_READ |
|
||||
FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA |
|
||||
STANDARD_RIGHTS_WRITE | SYNCHRONIZE;
|
||||
|
||||
assoc->value = finishSecurityDescriptor(
|
||||
assoc->daclEntries.size(),
|
||||
assoc->daclEntries.data(),
|
||||
assoc->dacl);
|
||||
if (!assoc->value) {
|
||||
return SecurityDescriptorDynamic();
|
||||
}
|
||||
|
||||
const PSECURITY_DESCRIPTOR ret = assoc->value.get();
|
||||
return SecurityDescriptorDynamic(ret, std::move(assoc));
|
||||
}
|
||||
|
||||
SecurityDescriptorLocal getObjectSecurityDescriptor(HANDLE handle) {
|
||||
PACL dacl = nullptr;
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
const DWORD errCode = GetSecurityInfo(handle, SE_KERNEL_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION |
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr, nullptr, &dacl, nullptr, &sd);
|
||||
if (errCode != ERROR_SUCCESS) {
|
||||
trace("error: GetSecurityInfo failed: %u",
|
||||
static_cast<unsigned>(errCode));
|
||||
return SecurityDescriptorLocal();
|
||||
} else {
|
||||
return SecurityDescriptorLocal(sd);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring sidToString(PSID sid) {
|
||||
wchar_t *sidString = NULL;
|
||||
BOOL success = ConvertSidToStringSidW(sid, &sidString);
|
||||
if (!success) {
|
||||
trace("error: ConvertSidToStringSidW failed");
|
||||
return std::wstring();
|
||||
}
|
||||
PointerLocal freer(sidString);
|
||||
return std::wstring(sidString);
|
||||
}
|
||||
|
||||
SidLocal stringToSid(const std::wstring &str) {
|
||||
// Cast the string from const wchar_t* to LPWSTR because the function is
|
||||
// incorrectly prototyped in the MinGW sddl.h header. The API does not
|
||||
// modify the string -- it is correctly prototyped as taking LPCWSTR in
|
||||
// MinGW-w64, MSVC, and MSDN.
|
||||
PSID psid = nullptr;
|
||||
BOOL success = ConvertStringSidToSidW(const_cast<LPWSTR>(str.c_str()),
|
||||
&psid);
|
||||
if (!success) {
|
||||
trace("error: ConvertStringSidToSidW failed");
|
||||
return SidLocal();
|
||||
}
|
||||
return SidLocal(psid);
|
||||
}
|
||||
|
||||
// The two string<->SD conversion APIs are used for debugging purposes.
|
||||
// Converting a string to an SD is convenient, but it's too slow for ordinary
|
||||
// use. The APIs exist in XP and up, but the MinGW headers don't declare the
|
||||
// functions. (Of course, MinGW-w64 and MSVC both *do* declare them.)
|
||||
|
||||
typedef BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorW_t(
|
||||
LPCWSTR StringSecurityDescriptor,
|
||||
DWORD StringSDRevision,
|
||||
PSECURITY_DESCRIPTOR *SecurityDescriptor,
|
||||
PULONG SecurityDescriptorSize);
|
||||
|
||||
typedef BOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW_t(
|
||||
PSECURITY_DESCRIPTOR SecurityDescriptor,
|
||||
DWORD RequestedStringSDRevision,
|
||||
SECURITY_INFORMATION SecurityInformation,
|
||||
LPWSTR *StringSecurityDescriptor,
|
||||
PULONG StringSecurityDescriptorLen);
|
||||
|
||||
const DWORD kSDDL_REVISION_1 = 1;
|
||||
|
||||
SecurityDescriptorLocal stringToSd(const std::wstring &str) {
|
||||
OsModule advapi32(L"advapi32.dll");
|
||||
const auto proc =
|
||||
reinterpret_cast<ConvertStringSecurityDescriptorToSecurityDescriptorW_t*>(
|
||||
advapi32.proc("ConvertStringSecurityDescriptorToSecurityDescriptorW"));
|
||||
if (proc == nullptr) {
|
||||
// The OsModule class already logged an error message.
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
PSECURITY_DESCRIPTOR desc = nullptr;
|
||||
if (!proc(str.c_str(),
|
||||
kSDDL_REVISION_1,
|
||||
&desc,
|
||||
nullptr)) {
|
||||
trace("error: ConvertStringSecurityDescriptorToSecurityDescriptorW failed: "
|
||||
"str='%ls'", str.c_str());
|
||||
return SecurityDescriptorLocal();
|
||||
}
|
||||
return SecurityDescriptorLocal(desc);
|
||||
}
|
||||
|
||||
// Generates a human-readable representation of a PSECURITY_DESCRIPTOR.
|
||||
// Returns an empty string on error.
|
||||
std::wstring sdToString(PSECURITY_DESCRIPTOR sd) {
|
||||
OsModule advapi32(L"advapi32.dll");
|
||||
const auto proc =
|
||||
reinterpret_cast<ConvertSecurityDescriptorToStringSecurityDescriptorW_t*>(
|
||||
advapi32.proc("ConvertSecurityDescriptorToStringSecurityDescriptorW"));
|
||||
if (proc == nullptr) {
|
||||
// The OsModule class already logged an error message.
|
||||
return std::wstring();
|
||||
}
|
||||
wchar_t *sdString = nullptr;
|
||||
if (!proc(sd,
|
||||
kSDDL_REVISION_1,
|
||||
OWNER_SECURITY_INFORMATION |
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION,
|
||||
&sdString,
|
||||
nullptr)) {
|
||||
trace("error: ConvertSecurityDescriptorToStringSecurityDescriptor failed");
|
||||
return std::wstring();
|
||||
}
|
||||
PointerLocal freer(sdString);
|
||||
return std::wstring(sdString);
|
||||
}
|
||||
|
||||
// Vista added a useful flag to CreateNamedPipe, PIPE_REJECT_REMOTE_CLIENTS,
|
||||
// that rejects remote connections. Return this flag on Vista, or return 0
|
||||
// otherwise.
|
||||
DWORD rejectRemoteClientsPipeFlag() {
|
||||
// MinGW lacks this flag; MinGW-w64 has it.
|
||||
const DWORD kPIPE_REJECT_REMOTE_CLIENTS = 8;
|
||||
|
||||
OSVERSIONINFOW info = { sizeof(info) };
|
||||
if (!GetVersionExW(&info)) {
|
||||
trace("error: GetVersionExW failed: %u",
|
||||
static_cast<unsigned>(GetLastError()));
|
||||
return kPIPE_REJECT_REMOTE_CLIENTS;
|
||||
} else if (info.dwMajorVersion >= 6) {
|
||||
return kPIPE_REJECT_REMOTE_CLIENTS;
|
||||
} else {
|
||||
trace("Omitting PIPE_REJECT_REMOTE_CLIENTS on old OS (%d.%d)",
|
||||
static_cast<int>(info.dwMajorVersion),
|
||||
static_cast<int>(info.dwMinorVersion));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // winpty_shared namespace
|
110
src/shared/WindowsSecurity.h
Executable file
110
src/shared/WindowsSecurity.h
Executable file
@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2016 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 WINPTY_WINDOWS_SECURITY_H
|
||||
#define WINPTY_WINDOWS_SECURITY_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace winpty_shared {
|
||||
|
||||
struct LocalFreer {
|
||||
void operator()(void *ptr) {
|
||||
if (ptr != nullptr) {
|
||||
LocalFree(reinterpret_cast<HLOCAL>(ptr));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SidFreer {
|
||||
void operator()(PSID ptr) {
|
||||
if (ptr != nullptr) {
|
||||
FreeSid(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// An arbitrary value of copyable type V with an associated underlying object
|
||||
// of movable type A.
|
||||
template <typename V, typename A>
|
||||
class AssocValue {
|
||||
V m_v;
|
||||
A m_a;
|
||||
|
||||
public:
|
||||
V get() const { return m_v; }
|
||||
operator bool() const { return m_v; }
|
||||
AssocValue() : m_v {}, m_a {} {}
|
||||
AssocValue(V v, A &&a) : m_v(v), m_a(std::move(a)) {}
|
||||
AssocValue(AssocValue &&o) : m_v(o.m_v), m_a(std::move(o.m_a)) {
|
||||
o.m_v = V {};
|
||||
}
|
||||
AssocValue &operator=(AssocValue &&o) {
|
||||
m_v = o.m_v;
|
||||
m_a = std::move(o.m_a);
|
||||
o.m_v = V {};
|
||||
return *this;
|
||||
}
|
||||
AssocValue(const AssocValue &other) = delete;
|
||||
AssocValue &operator=(const AssocValue &other) = delete;
|
||||
};
|
||||
|
||||
class DynamicAssoc {
|
||||
public:
|
||||
virtual ~DynamicAssoc() {}
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<void, LocalFreer> PointerLocal;
|
||||
typedef std::unique_ptr<std::remove_pointer<PACL>::type, LocalFreer> AclLocal;
|
||||
typedef std::unique_ptr<std::remove_pointer<PSID>::type, SidFreer> SidDefault;
|
||||
typedef std::unique_ptr<std::remove_pointer<PSID>::type, LocalFreer> SidLocal;
|
||||
typedef AssocValue<PSID, std::unique_ptr<DynamicAssoc>> SidDynamic;
|
||||
typedef AssocValue<PSECURITY_DESCRIPTOR, std::unique_ptr<DynamicAssoc>>
|
||||
SecurityDescriptorDynamic;
|
||||
typedef std::unique_ptr<std::remove_pointer<PSECURITY_DESCRIPTOR>::type, LocalFreer>
|
||||
SecurityDescriptorLocal;
|
||||
|
||||
SidDynamic getOwnerSid();
|
||||
SidDefault wellKnownSid(const char *debuggingName,
|
||||
SID_IDENTIFIER_AUTHORITY authority,
|
||||
BYTE authorityCount,
|
||||
DWORD subAuthority0=0,
|
||||
DWORD subAuthority1=0);
|
||||
SidDefault builtinAdminsSid();
|
||||
SidDefault localSystemSid();
|
||||
SidDefault everyoneSid();
|
||||
SecurityDescriptorDynamic createPipeSecurityDescriptorOwnerFullControl();
|
||||
SecurityDescriptorDynamic createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
|
||||
SecurityDescriptorLocal getObjectSecurityDescriptor(HANDLE handle);
|
||||
std::wstring sidToString(PSID sid);
|
||||
SidLocal stringToSid(const std::wstring &str);
|
||||
SecurityDescriptorLocal stringToSd(const std::wstring &str);
|
||||
std::wstring sdToString(PSECURITY_DESCRIPTOR sd);
|
||||
DWORD rejectRemoteClientsPipeFlag();
|
||||
|
||||
} // winpty_shared namespace
|
||||
|
||||
#endif // WINPTY_WINDOWS_SECURITY_H
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2011-2012 Ryan Prichard
|
||||
// Copyright (c) 2011-2016 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
|
||||
@ -19,17 +19,37 @@
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#include "WinptyAssert.h"
|
||||
#include "DebugClient.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Calling the standard assert() function does not work in the agent because
|
||||
// the error message would be printed to the console, and the only way the
|
||||
// user can see the console is via a working agent! This custom assert
|
||||
// function instead sends the message to the DebugServer.
|
||||
#include "DebugClient.h"
|
||||
|
||||
void assertFail(const char *file, int line, const char *cond)
|
||||
{
|
||||
void assertTrace(const char *file, int line, const char *cond) {
|
||||
trace("Assertion failed: %s, file %s, line %d",
|
||||
cond, file, line);
|
||||
abort();
|
||||
}
|
||||
|
||||
#ifdef WINPTY_AGENT_ASSERT
|
||||
|
||||
void agentShutdown() {
|
||||
HWND hwnd = GetConsoleWindow();
|
||||
if (hwnd != NULL) {
|
||||
PostMessage(hwnd, WM_CLOSE, 0, 0);
|
||||
Sleep(30000);
|
||||
trace("Agent shutdown: WM_CLOSE did not end agent process");
|
||||
} else {
|
||||
trace("Agent shutdown: GetConsoleWindow() is NULL");
|
||||
}
|
||||
// abort() prints a message to the console, and if it is frozen, then the
|
||||
// process would hang, so instead use exit(). (We shouldn't ever get here,
|
||||
// though, because the WM_CLOSE message should have ended this process.)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void agentAssertFail(const char *file, int line, const char *cond) {
|
||||
assertTrace(file, line, cond);
|
||||
agentShutdown();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2011-2012 Ryan Prichard
|
||||
// Copyright (c) 2011-2016 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,8 +21,44 @@
|
||||
#ifndef WINPTY_ASSERT_H
|
||||
#define WINPTY_ASSERT_H
|
||||
|
||||
#define ASSERT(x) do { if (!(x)) assertFail(__FILE__, __LINE__, #x); } while(0)
|
||||
#ifdef WINPTY_AGENT_ASSERT
|
||||
|
||||
void assertFail(const char *file, int line, const char *cond);
|
||||
void agentShutdown();
|
||||
void agentAssertFail(const char *file, int line, const char *cond);
|
||||
|
||||
// Calling the standard assert() function does not work in the agent because
|
||||
// the error message would be printed to the console, and the only way the
|
||||
// user can see the console is via a working agent! Moreover, the console may
|
||||
// be frozen, so attempting to write to it would block forever. This custom
|
||||
// assert function instead sends the message to the DebugServer, then attempts
|
||||
// to close the console, then quietly exits.
|
||||
#define ASSERT(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
agentAssertFail(__FILE__, __LINE__, #cond); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
void assertTrace(const char *file, int line, const char *cond);
|
||||
|
||||
// In the other targets, log the assert failure to the debugserver, then fail
|
||||
// using the ordinary assert mechanism. In case assert is compiled out, fail
|
||||
// using abort. The amount of code inlined is unfortunate, but asserts aren't
|
||||
// used much outside the agent.
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#define ASSERT_CONDITION(cond) (false && (cond))
|
||||
#define ASSERT(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
assertTrace(__FILE__, __LINE__, #cond); \
|
||||
assert(ASSERT_CONDITION(#cond)); \
|
||||
abort(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // WINPTY_ASSERT_H
|
||||
|
@ -1,92 +0,0 @@
|
||||
// Copyright (c) 2012 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 C99_SNPRINTF_H
|
||||
#define C99_SNPRINTF_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#if _MSC_VER < 1800
|
||||
/* MSVC does not define C99's va_copy, so define one for it. It appears that
|
||||
* with MSVC, a va_list is a char*, not an array type, so va_copy is a simple
|
||||
* assignment. On MSVC 2012, there is a VC/include/vadefs.h file defining
|
||||
* va_list and the va_XXX routines, which has code for x86, x86-64, and ARM. */
|
||||
#define c99_va_copy(dest, src) ((dest) = (src))
|
||||
#else
|
||||
/* Visual C++ 2013 added va_copy. */
|
||||
#define c99_va_copy(dest, src) va_copy(dest, src)
|
||||
#endif
|
||||
|
||||
static inline int c99_vsnprintf(
|
||||
char* str,
|
||||
size_t size,
|
||||
const char* format,
|
||||
va_list ap)
|
||||
{
|
||||
va_list apcopy;
|
||||
int count = -1;
|
||||
if (size != 0) {
|
||||
c99_va_copy(apcopy, ap);
|
||||
count = _vsnprintf_s(str, size, _TRUNCATE, format, apcopy);
|
||||
va_end(apcopy);
|
||||
}
|
||||
if (count == -1) {
|
||||
c99_va_copy(apcopy, ap);
|
||||
count = _vscprintf(format, apcopy);
|
||||
va_end(apcopy);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define c99_va_copy(dest, src) (va_copy(dest, src))
|
||||
|
||||
static inline int c99_vsnprintf(
|
||||
char* str,
|
||||
size_t size,
|
||||
const char* format,
|
||||
va_list ap)
|
||||
{
|
||||
return vsnprintf(str, size, format, ap);
|
||||
}
|
||||
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
static inline int c99_snprintf(
|
||||
char* str,
|
||||
size_t size,
|
||||
const char* format,
|
||||
...)
|
||||
{
|
||||
int count;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
count = c99_vsnprintf(str, size, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif /* C99_SNPRINTF_H */
|
58
src/shared/cxx11_mutex.h
Executable file
58
src/shared/cxx11_mutex.h
Executable 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
|
19
src/agent/Coord.cc → src/shared/cxx11_noexcept.h
Normal file → Executable file
19
src/agent/Coord.cc → src/shared/cxx11_noexcept.h
Normal file → Executable 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
|
||||
@ -18,12 +18,13 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#include "Coord.h"
|
||||
#include <stdio.h>
|
||||
#ifndef WINPTY_CXX11_NOEXCEPT_H
|
||||
#define WINPTY_CXX11_NOEXCEPT_H
|
||||
|
||||
std::string Coord::toString() const
|
||||
{
|
||||
char ret[32];
|
||||
sprintf(ret, "(%d,%d)", X, Y);
|
||||
return std::string(ret);
|
||||
}
|
||||
#if defined(__GNUC__)
|
||||
#define WINPTY_NOEXCEPT noexcept
|
||||
#else
|
||||
#define WINPTY_NOEXCEPT
|
||||
#endif
|
||||
|
||||
#endif // WINPTY_CXX11_NOEXCEPT_H
|
83
src/shared/winpty_snprintf.cc
Executable file
83
src/shared/winpty_snprintf.cc
Executable file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2012-2016 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.
|
||||
|
||||
// To work around a MinGW bug, we must call these functions from a separate
|
||||
// translation unit and carefully control the order that system headers are
|
||||
// included. This works:
|
||||
//
|
||||
// #include <stdio.h>
|
||||
// #include <string>
|
||||
//
|
||||
// This does not:
|
||||
//
|
||||
// #include <string>
|
||||
// #include <stdio.h>
|
||||
//
|
||||
// With the latter, _vscwprintf is not declared. This bug affects MinGW's
|
||||
// official distribution, but not Cygwin's MinGW distribution. It also does
|
||||
// not affect MinGW-w64.
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "winpty_snprintf.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
bool winpty_vsnprintf(char *out, size_t size,
|
||||
const char *format, va_list ap) {
|
||||
assert(out != NULL && size != 0);
|
||||
out[0] = '\0';
|
||||
#if defined(__CYGWIN__) || defined(__MSYS__)
|
||||
const int count = vsnprintf(out, size, format, ap);
|
||||
#elif defined(_MSC_VER)
|
||||
const int count = _vsnprintf_s(out, size, _TRUNCATE, format, ap);
|
||||
#else
|
||||
const int count = _vsnprintf(out, size, format, ap);
|
||||
#endif
|
||||
out[size - 1] = '\0';
|
||||
return count >= 0 && static_cast<size_t>(count) < size;
|
||||
}
|
||||
|
||||
#if !defined(__CYGWIN__) && !defined(__MSYS__)
|
||||
|
||||
bool winpty_vswprintf(wchar_t *out, size_t size,
|
||||
const wchar_t *format, va_list ap) {
|
||||
assert(out != NULL && size != 0);
|
||||
out[0] = L'\0';
|
||||
#if defined(_MSC_VER)
|
||||
const int count = _vsnwprintf_s(out, size, _TRUNCATE, format, ap);
|
||||
#else
|
||||
const int count = _vsnwprintf(out, size, format, ap);
|
||||
#endif
|
||||
out[size - 1] = L'\0';
|
||||
return count >= 0 && static_cast<size_t>(count) < size;
|
||||
}
|
||||
|
||||
int winpty_vscprintf(const char *format, va_list ap) {
|
||||
return _vscprintf(format, ap);
|
||||
}
|
||||
|
||||
int winpty_vscwprintf(const wchar_t *format, va_list ap) {
|
||||
return _vscwprintf(format, ap);
|
||||
}
|
||||
|
||||
#endif // !defined(__CYGWIN__) && !defined(__MSYS__)
|
164
src/shared/winpty_snprintf.h
Normal file
164
src/shared/winpty_snprintf.h
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2012-2016 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.
|
||||
|
||||
// MSVC did not define an snprintf function prior to MSVC 2015, and the
|
||||
// snprintf in MSVC 2015 is undocumented as of this writing (Jan 2016). This
|
||||
// header defines portable alternatives. Specifically, this header defines
|
||||
// winpty_snprintf and winpty_vsnprintf.
|
||||
//
|
||||
// These two functions are not C99-conformant, because C99 conformance is
|
||||
// difficult to achieve. Details:
|
||||
//
|
||||
// - On Cygwin, the format string is GNU/ISO-C style. (e.g. PRIx64 is 'llx',
|
||||
// and Cygwin's printf does not recognize '%I64x' at all.)
|
||||
//
|
||||
// - On native Windows (MinGW/MinGW-w64/MSVC), the format string is Microsoft
|
||||
// style. e.g. PRIx64 is 'I64x'. 'llx' tends to work on Vista and up, but
|
||||
// it is truncated on XP. 'z' for size_t does not work until MSVC 2015.
|
||||
//
|
||||
// - Do not use inttypes.h. If MinGW-w64 is configured with
|
||||
// -D__USE_MINGW_ANSI_STDIO=1, then its PRIx64 changes to 'll', but this
|
||||
// header is still bypassing MinGW overrides and calling MSVCRT.DLL
|
||||
// directly, and 'll' breaks on XP. MinGW doesn't change its PRIx64. Use
|
||||
// WINPTY_PRI??? instead.
|
||||
//
|
||||
// - The winpty_[v]s[nw]printf functions returns true/false for
|
||||
// success/failure. If the buffer is too small, the function writes as many
|
||||
// bytes as possible and returns false. The buffer will always be
|
||||
// NUL-terminated on return.
|
||||
//
|
||||
// - The winpty_[v]c[nw]printf functions are only implemented on native
|
||||
// Windows. AFAICT, it is actually impossible to implement the wide-string
|
||||
// version of these functions in standard C and on Linux. The
|
||||
// winpty_[v]swprintf functions are only implemented on native Windows,
|
||||
// because, even though Cygwin has vswprintf, MSYS1 does not.
|
||||
|
||||
#ifndef WINPTY_SNPRINTF_H
|
||||
#define WINPTY_SNPRINTF_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
|
||||
// 64-bit format strings.
|
||||
|
||||
#if defined(__CYGWIN__) || defined(__MSYS__)
|
||||
#define WINPTY_PRId64 "lld"
|
||||
#define WINPTY_PRIu64 "llu"
|
||||
#define WINPTY_PRIx64 "llx"
|
||||
#define WINPTY_PRIX64 "llX"
|
||||
#else
|
||||
#define WINPTY_PRId64 "I64d"
|
||||
#define WINPTY_PRIu64 "I64u"
|
||||
#define WINPTY_PRIx64 "I64x"
|
||||
#define WINPTY_PRIX64 "I64X"
|
||||
#endif
|
||||
|
||||
// Format checking declarations
|
||||
|
||||
// The GCC printf checking only works with narrow strings. It prints the error
|
||||
// "error: format string argument is not a string type" for the wide functions.
|
||||
#if defined(__CYGWIN__) || defined(__MSYS__)
|
||||
#define WINPTY_SNPRINTF_FORMAT __attribute__((format(printf, 3, 4)))
|
||||
#define WINPTY_SNPRINTF_ARRAY_FORMAT __attribute__((format(printf, 2, 3)))
|
||||
#elif defined(__GNUC__)
|
||||
#define WINPTY_SNPRINTF_FORMAT __attribute__((format(ms_printf, 3, 4)))
|
||||
#define WINPTY_SNPRINTF_ARRAY_FORMAT __attribute__((format(ms_printf, 2, 3)))
|
||||
#define WINPTY_SCPRINTF_FORMAT __attribute__((format(ms_printf, 1, 2)))
|
||||
#else
|
||||
#define WINPTY_SNPRINTF_FORMAT
|
||||
#define WINPTY_SNPRINTF_ARRAY_FORMAT
|
||||
#define WINPTY_SCPRINTF_FORMAT
|
||||
#endif
|
||||
|
||||
inline bool winpty_snprintf(char *out, size_t size,
|
||||
const char *format, ...) WINPTY_SNPRINTF_FORMAT;
|
||||
|
||||
template <size_t size> bool winpty_snprintf(char (&out)[size],
|
||||
const char *format, ...)
|
||||
WINPTY_SNPRINTF_ARRAY_FORMAT;
|
||||
|
||||
// Inline definitions
|
||||
|
||||
#define WINPTY_SNPRINTF_FORWARD(type, expr) \
|
||||
do { \
|
||||
va_list ap; \
|
||||
va_start(ap, format); \
|
||||
type temp_ = (expr); \
|
||||
va_end(ap); \
|
||||
return temp_; \
|
||||
} while (false)
|
||||
|
||||
bool winpty_vsnprintf(char *out, size_t size,
|
||||
const char *format, va_list ap);
|
||||
|
||||
template <size_t size> bool winpty_vsnprintf(
|
||||
char (&out)[size],
|
||||
const char *format, va_list ap) {
|
||||
return winpty_vsnprintf(out, size, format, ap);
|
||||
}
|
||||
|
||||
inline bool winpty_snprintf(char *out, size_t size,
|
||||
const char *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(bool, winpty_vsnprintf(out, size, format, ap));
|
||||
}
|
||||
|
||||
template <size_t size> bool winpty_snprintf(char (&out)[size],
|
||||
const char *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(bool, winpty_vsnprintf(out, size, format, ap));
|
||||
}
|
||||
|
||||
#if !defined(__CYGWIN__) && !defined(__MSYS__)
|
||||
|
||||
bool winpty_vswprintf(wchar_t *out, size_t size,
|
||||
const wchar_t *format, va_list ap);
|
||||
|
||||
template <size_t size> bool winpty_vswprintf(
|
||||
wchar_t (&out)[size],
|
||||
const wchar_t *format, va_list ap) {
|
||||
return winpty_vswprintf(out, size, format, ap);
|
||||
}
|
||||
|
||||
inline bool winpty_swprintf(wchar_t *out, size_t size,
|
||||
const wchar_t *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(bool, winpty_vswprintf(out, size, format, ap));
|
||||
}
|
||||
|
||||
template <size_t size> bool winpty_swprintf(wchar_t (&out)[size],
|
||||
const wchar_t *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(bool, winpty_vswprintf(out, size, format, ap));
|
||||
}
|
||||
|
||||
int winpty_vscprintf(const char *format, va_list ap);
|
||||
int winpty_vscwprintf(const wchar_t *format, va_list ap);
|
||||
|
||||
inline int winpty_scprintf(const char *format, ...) WINPTY_SCPRINTF_FORMAT;
|
||||
inline int winpty_scprintf(const char *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(int, winpty_vscprintf(format, ap));
|
||||
}
|
||||
|
||||
inline int winpty_scwprintf(const wchar_t *format, ...) {
|
||||
WINPTY_SNPRINTF_FORWARD(int, winpty_vscwprintf(format, ap));
|
||||
}
|
||||
|
||||
#endif // !defined(__CYGWIN__) && !defined(__MSYS__)
|
||||
|
||||
#endif // WINPTY_SNPRINTF_H
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include <winpty.h>
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/UnixCtrlChars.h"
|
||||
#include "../shared/WinptyAssert.h"
|
||||
#include "../shared/WinptyVersion.h"
|
||||
#include "InputHandler.h"
|
||||
#include "OutputHandler.h"
|
||||
@ -349,29 +350,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 +426,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 +442,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 +472,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;
|
||||
}
|
||||
|
@ -20,14 +20,18 @@
|
||||
|
||||
ALL_TARGETS += build/$(UNIX_ADAPTER_EXE)
|
||||
|
||||
$(eval $(call def_unix_target,unix-adapter,))
|
||||
|
||||
UNIX_ADAPTER_OBJECTS = \
|
||||
build/unix/unix-adapter/InputHandler.o \
|
||||
build/unix/unix-adapter/OutputHandler.o \
|
||||
build/unix/unix-adapter/Util.o \
|
||||
build/unix/unix-adapter/WakeupFd.o \
|
||||
build/unix/unix-adapter/main.o \
|
||||
build/unix/shared/DebugClient.o \
|
||||
build/unix/shared/WinptyVersion.o
|
||||
build/unix-adapter/unix-adapter/InputHandler.o \
|
||||
build/unix-adapter/unix-adapter/OutputHandler.o \
|
||||
build/unix-adapter/unix-adapter/Util.o \
|
||||
build/unix-adapter/unix-adapter/WakeupFd.o \
|
||||
build/unix-adapter/unix-adapter/main.o \
|
||||
build/unix-adapter/shared/DebugClient.o \
|
||||
build/unix-adapter/shared/WinptyAssert.o \
|
||||
build/unix-adapter/shared/WinptyVersion.o \
|
||||
build/unix-adapter/shared/winpty_snprintf.o
|
||||
|
||||
build/$(UNIX_ADAPTER_EXE) : $(UNIX_ADAPTER_OBJECTS) build/winpty.dll
|
||||
@echo Linking $@
|
||||
|
@ -20,6 +20,7 @@
|
||||
'defines' : [
|
||||
'UNICODE',
|
||||
'_UNICODE',
|
||||
'WINVER=0x0501',
|
||||
'_WIN32_WINNT=0x0501',
|
||||
'NOMINMAX',
|
||||
'WINPTY_VERSION=<!(cmd /c "cd .. && type VERSION.txt")',
|
||||
@ -31,10 +32,11 @@
|
||||
{
|
||||
'target_name' : 'winpty-agent',
|
||||
'type' : 'executable',
|
||||
'include_dirs' : [
|
||||
'include',
|
||||
'defines' : [
|
||||
'WINPTY_AGENT_ASSERT',
|
||||
],
|
||||
'libraries' : [
|
||||
'-ladvapi32',
|
||||
'-luser32',
|
||||
],
|
||||
'sources' : [
|
||||
@ -47,7 +49,6 @@
|
||||
'agent/ConsoleLine.cc',
|
||||
'agent/ConsoleLine.h',
|
||||
'agent/Coord.h',
|
||||
'agent/Coord.cc',
|
||||
'agent/DebugShowInput.h',
|
||||
'agent/DebugShowInput.cc',
|
||||
'agent/DefaultInputMap.h',
|
||||
@ -63,7 +64,6 @@
|
||||
'agent/NamedPipe.cc',
|
||||
'agent/SimplePool.h',
|
||||
'agent/SmallRect.h',
|
||||
'agent/SmallRect.cc',
|
||||
'agent/Terminal.h',
|
||||
'agent/Terminal.cc',
|
||||
'agent/UnicodeEncoding.h',
|
||||
@ -72,15 +72,22 @@
|
||||
'agent/main.cc',
|
||||
'shared/AgentMsg.h',
|
||||
'shared/Buffer.h',
|
||||
'shared/Buffer.cc',
|
||||
'shared/DebugClient.h',
|
||||
'shared/DebugClient.cc',
|
||||
'shared/GenRandom.h',
|
||||
'shared/GenRandom.cc',
|
||||
'shared/OsModule.h',
|
||||
'shared/StringBuilder.h',
|
||||
'shared/UnixCtrlChars.h',
|
||||
'shared/WindowsSecurity.h',
|
||||
'shared/WindowsSecurity.cc',
|
||||
'shared/WinptyAssert.h',
|
||||
'shared/WinptyAssert.cc',
|
||||
'shared/WinptyVersion.h',
|
||||
'shared/WinptyVersion.cc',
|
||||
'shared/c99_snprintf.h',
|
||||
'shared/winpty_snprintf.h',
|
||||
'shared/winpty_snprintf.cc',
|
||||
'shared/winpty_wcsnlen.cc',
|
||||
'shared/winpty_wcsnlen.h',
|
||||
],
|
||||
@ -88,20 +95,40 @@
|
||||
{
|
||||
'target_name' : 'winpty',
|
||||
'type' : 'shared_library',
|
||||
'include_dirs' : [
|
||||
'include',
|
||||
'defines' : [
|
||||
'COMPILING_WINPTY_DLL',
|
||||
],
|
||||
'libraries' : [
|
||||
'-ladvapi32',
|
||||
'-luser32',
|
||||
],
|
||||
'sources' : [
|
||||
'include/winpty.h',
|
||||
'include/winpty_constants.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/GenRandom.h',
|
||||
'shared/GenRandom.cc',
|
||||
'shared/StringBuilder.h',
|
||||
'shared/WindowsSecurity.h',
|
||||
'shared/WindowsSecurity.cc',
|
||||
'shared/WinptyAssert.h',
|
||||
'shared/WinptyAssert.cc',
|
||||
'shared/cxx11_mutex.h',
|
||||
'shared/cxx11_noexcept.h',
|
||||
'shared/winpty_snprintf.h',
|
||||
'shared/winpty_snprintf.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -109,6 +136,17 @@
|
||||
'type' : 'executable',
|
||||
'sources' : [
|
||||
'debugserver/DebugServer.cc',
|
||||
'shared/DebugClient.h',
|
||||
'shared/DebugClient.cc',
|
||||
'shared/WindowsSecurity.h',
|
||||
'shared/WindowsSecurity.cc',
|
||||
'shared/WinptyAssert.h',
|
||||
'shared/WinptyAssert.cc',
|
||||
'shared/winpty_snprintf.h',
|
||||
'shared/winpty_snprintf.cc',
|
||||
],
|
||||
'libraries' : [
|
||||
'-ladvapi32',
|
||||
],
|
||||
}
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user