Compare commits

...

26 Commits

Author SHA1 Message Date
Ryan Prichard
9675a911a1 Merge branch 'master' into libwinpty-rewrite 2016-02-21 01:35:57 -06:00
Ryan Prichard
41762f3851 Link statically to the MSVC runtime library in configurations.gypi. 2016-01-24 02:26:58 -06:00
Ryan Prichard
b844635158 Remove support for the MinGW compiler packaged with Cygwin.
MinGW-w64 is still supported, as is the MinGW compiler distributed by
mingw.org.

Cygwin's MinGW toolchain uses the 4.0-1 mingw-runtime package by
default, which appears to correspond to the "WSL-4" 4.x version of the
MinGW project's mingwrt packages.  (The version numbers in the
/usr/i686-pc-mingw32/sys-root/mingw/include/_mingw.h file are also
consistent with this theory.)

The WSL-4 branch was repudiated by the MinGW project as a mistake [1].
The MinGW project claims no responsibility for Cygwin's MinGW packages
[2].  There were several releases in short succession [3]:
  4.0.3 2013-09-22
  4.0.2 2013-09-15
  4.0.1 2013-09-14
  4.0.0 2013-08-23
There have been no 4.x releases since then, though there have been more
3.x releases (3.21 and 3.21.1).  The FILE_FLAG_FIRST_PIPE_INSTANCE bug
that this project has worked around was fixed in 4.0.3 [4], which was
never propagated to the Cygwin package.

The MinGW packages were apparantly orphaned on 2014-09-18 [5] and are
still orphaned [6].

[1] http://sourceforge.net/p/mingw/bugs/1673/#5260/41ec
[2] http://sourceforge.net/p/mingw/bugs/1673/#2556/35d4
[3] http://sourceforge.net/projects/mingw/files/MinGW/Base/mingwrt/
[4] http://sourceforge.net/p/mingw/bugs/2050/
[5] https://cygwin.com/ml/cygwin/2014-09/msg00289.html
[6] https://cygwin.com/cygwin-pkg-maint
2016-01-23 06:19:56 -06:00
Ryan Prichard
9b69f67f56 Use precompiled headers when compiling with GCC.
The unix-adapter doesn't use PCH, because MSYS1's compiler doesn't support
them.
2016-01-23 05:50:30 -06:00
Ryan Prichard
e3c669ae56 Avoid invoking dirname and git for each compiled C++ source file 2016-01-23 04:36:44 -06:00
Ryan Prichard
7f1045972c Work around the MinGW -std=c++11 bug using -std=gnu++11. 2016-01-23 04:36:04 -06:00
Ryan Prichard
cfb5f81de3 Adjust error handling in libwinpty
* Use ASSERT() where the API is being misused.  Also use it when
   WaitForMultipleObjects or WriteFile return a value I don't expect to
   ever see.

 * Allow passing err to winpty_error_{code,msg}, though.  Instead of
   complaining about the NULL error pointer, instead return
   WINPTY_ERROR_SUCCESS and L"Success".
2016-01-23 04:12:25 -06:00
Ryan Prichard
6deb5e2c26 Make WinptyAssert.h behave more sensibly, both inside and outside the agent
* In agent: if the console is frozen, then calling abort hangs forever,
   because abort tries to write to stderr, which blocks.  Instead,
   use WM_CLOSE to close the console window.  If that doesn't work after
   waiting a while, then exit.

   We need to do the same thing doing normal agent shutdown, so factor out
   an agentShutdown function.  It's a bit unhappy living in
   WinptyAssert.cc, but it works.

 * Outside agent: trace an assertion failure message, then delegate to the
   ordinary assert() function.  If that's somehow turned off, then call
   abort().
2016-01-18 01:41:59 -06:00
Ryan Prichard
873733faa4 Move the toString functions into Coord.h and SmallRect.h.
I don't think these functions are actually called anywhere -- they only
exist for debugging.  Inlining them will probably reduce build times.
2016-01-18 00:28:15 -06:00
Ryan Prichard
3da3381859 Stop spawning cat/tr for each compiled source file 2016-01-18 00:16:45 -06:00
Ryan Prichard
80d4faa72c Compile each target's source files separately from other targets.
The gyp file already worked this way, but now the Makefile does too.

The motivation is to let the shared source files have different error
handling behavior (e.g. assert/ASSERT/stderr-print/abort/exception) in
winpty.dll and the other targets.
2016-01-18 00:10:02 -06:00
Ryan Prichard
834195074a The debugserver also needs FILE_FLAG_FIRST_PIPE_INSTANCE now. 2016-01-17 17:58:53 -06:00
Ryan Prichard
413f17a81e Rename c99_snprintf to winpty_snprintf and expand the header file.
* The c99_snprintf function was not actually a C99-conformant snprintf
   function, except perhaps when compiled for Cygwin/MSYS.

 * MSVCRT.DLL exposes _vsnprintf, _vswprintf, _vscprintf, and _vscwprintf,
   which are the four most relevant and fundamental printf functions.  It
   *doesn't* expose a function like _scprintf, even though MSDN documents
   it.  It seems that the simple wrapper functions are linked statically
   even when linking dynamically to the MS CRT.

   It appears that MinGW and MinGW-w64 generallY provide these four
   functions in all configurations.  Therefore, for predictable behavior,
   always use these functions.

 * Examples of non-predictability:

    - MinGW declares _vscprintf but not _scprintf.  MinGW-w64 declares
      both.
    - MinGW and MinGW-w64 sometimes override some printf functions with
      their own versions.  (It's easy to tell when this happens because
      "%zd" starts working.)  With MinGW, -std=c99 enables the overrides,
      as does -std=c++11.  -std=gnu++11 does NOT enable the overrides.
    - When the overrides are not in effect, MinGW declares swprintf as
      taking no size parameter.  When the overrides ARE in effect, MinGW
      does not declare swprintf.  MinGW-w64 and MSVC prefer swprintf with
      a size parameter, but they overload it in C++ mode to allow calling
      it either way.  Cygwin and ISO C require the size parameter.
    - With overrides off, MinGW's snprintf is still the MinGW override
      function.  MinGW-w64's snprintf instead calls MSVCRT.DLL _snprintf.
      MinGW-w64 changes its PRI??? strings when overrides are enabled, but
      MinGW does not.

 * In the native Windows mode, which I'm more interested in anyway, it's
   fairly easy to calculate the length of a formatted string before
   outputting it.  Add a few wrapper functions that do this.  I could
   enhance StringBuilder with easy ways to add stdio-formatted strings.
   Of course, these sizes are only valid if they're computed by the same
   printf implementation that writes into the string, and they will be.

Work around a MinGW header-file non-idempotency bug w.r.t _vscwprintf
2016-01-17 17:58:25 -06:00
Ryan Prichard
ede3244c26 Switch from std::[w]stringstream/std::cout to [W]StringBuilder/fwrite
Remove all references to the iostream and sstream headers from code in the
src directory.
2016-01-17 00:40:28 -06:00
Ryan Prichard
cf76665cfe Add a StringBuilder class that resembles an efficient std::stringstream 2016-01-16 23:46:15 -06:00
Ryan Prichard
ba0e3a49fd Remove a few unused assert.h includes. 2016-01-16 15:09:24 -06:00
Ryan Prichard
f2c4f6a59b Add a clean-msvs target for cleaning up after gyp and VisualStudio/msbuild 2016-01-16 13:27:52 -06:00
Ryan Prichard
f04774eb06 Try to fix an issue where make fails if a header file disappears
Currently, when a header file is removed (or renamed), there is a stale
dependency file still referring to the header, and GNU make aborts, because
the header is missing.  Attempt to work around this by adding a pattern
rule to generate any header in `src/`.  The rule prints a warning message.

The C source file whose header was missing will be recompiled because a
dependency was rebuilt.  Once it is, the removed header should be removed
from the dependency file.

(Aside: FWIW, the "ninja" build tool knows directly about depfiles, and it
does not have this problem.)
2016-01-16 12:49:05 -06:00
Ryan Prichard
840847624d Split error codes and flags into a separate file for the agent's sake. 2016-01-16 02:45:33 -06:00
Ryan Prichard
f6c6f0558e Add __attribute__((format)) to trace and c99_snprintf, fix two mismatches 2016-01-16 00:03:27 -06:00
Ryan Prichard
a532a7ce3b winpty-debugserver: add an --everyone flag to let other users log messages
* In general, harmonize the debugserver named pipe with the other named
   pipe instances: don't let remote users log messages and fail if the
   pipe already exists.
2016-01-15 23:41:52 -06:00
Ryan Prichard
1ec380ad61 Improve named pipe security
* Set the PIPE_REJECT_REMOTE_CLIENTS flag on Vista and up.

 * Set a DACL on named pipes that gives full control to the built-in
   administrators, the local system account, and the current security
   token's owner SID.  This is the same DACL that is used by default,
   except that the default also grants read access to the Everyone group
   and the anonymous account.

 * Also define WINVER=0x0501, because MinGW's sddl.h only defines
   ConvertSidToStringSidW and ConvertStringSidToSidW if WINVER is defined.
   (Ordinarily, defining _WIN32_WINNT is sufficient, and it's even
   sufficient with the i686-pc-mingw32-g++ compiler packaged with Cygwin.)

 * The createSecurityDescriptorOwnerFullControlEveryoneWrite function is
   not currently used (or tested), but I think I'll use it in the debug
   server to allow collecting trace output from other accounts on the
   machine.  (I think I'll make that behavior optional.)

I tested this commit with all of the supported compilers: MSYS1 MinGW,
Cygwin MinGW, MSYS2 MinGW-w64, Cygwin MinGW-w64, MSVC2013, and MSVC2015.
The code compiles and runs in all of them.  I also examined the DACL
attached to the control pipe, and its SDDL string looked correct.
2016-01-15 23:35:01 -06:00
Ryan Prichard
1fafbc2ef5 Strengthen unique pipe name generation.
* PIDs are recycled, so include the system time.

 * A program running on the same machine could predict the names of pipes
   winpty uses and block winpty, causing a denial-of-service.  I'm not sure
   this is really a concern, but I suppose in principle it is?  Try to
   guard against it by appending random bytes to the pipe name.  The
   CreatePrivateNamespace API looks relevant, but I'm not sure it applies
   to named pipes, and it's Vista only.
2016-01-15 18:55:43 -06:00
Ryan Prichard
95be1c291f Remove the ASSERT from OsModule to make it more suitable for winpty.dll 2016-01-15 18:50:24 -06:00
Ryan Prichard
fe60984ce4 Work around -std=c++11 MinGW compiler problem
This bug was already fixed 5 months ago, but AFAICT MinGW.org hasn't
released it yet.  MinGW-w64 is unaffected, as is the MinGW compiler
packaged with Cygwin.
2016-01-15 03:21:50 -06:00
Ryan Prichard
6b31bd9d8c Initial draft of a rewritten libwinpty with a new API.
The new API improves upon the old API in a number of ways:

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

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

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

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

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

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

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

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

As of this libwinpty rewrite, all code outside the UNIX adapter uses a
subset of C++11.  The code will build with MSVC2013 or newer.  The oldest
MinGW G++ I see on my machine is the somewhat broken MinGW (not MinGW-w64)
compiler distributed with Cygwin.  That compiler is G++ 4.7.3, which is
new enough.
2016-01-15 03:21:50 -06:00
57 changed files with 3742 additions and 1000 deletions

View File

@ -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?)"

View File

@ -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
View File

@ -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.

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

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

View File

@ -43,18 +43,27 @@ NamedPipe::~NamedPipe()
// Returns true if anything happens (data received, data sent, pipe error).
bool NamedPipe::serviceIo(std::vector<HANDLE> *waitHandles)
{
if (m_handle == NULL)
if (m_handle == NULL) {
return false;
int readBytes = m_inputWorker->service();
int writeBytes = m_outputWorker->service();
}
int readBytes = 0;
int writeBytes = 0;
HANDLE readHandle = NULL;
HANDLE writeHandle = NULL;
if (m_inputWorker != NULL) {
readBytes = m_inputWorker->service();
readHandle = m_inputWorker->getWaitEvent();
}
if (m_outputWorker != NULL) {
writeBytes = m_outputWorker->service();
writeHandle = m_outputWorker->getWaitEvent();
}
if (readBytes == -1 || writeBytes == -1) {
closePipe();
return true;
}
if (m_inputWorker->getWaitEvent() != NULL)
waitHandles->push_back(m_inputWorker->getWaitEvent());
if (m_outputWorker->getWaitEvent() != NULL)
waitHandles->push_back(m_outputWorker->getWaitEvent());
if (readHandle != NULL) { waitHandles->push_back(readHandle); }
if (writeHandle != NULL) { waitHandles->push_back(writeHandle); }
return readBytes > 0 || writeBytes > 0;
}
@ -180,17 +189,19 @@ int NamedPipe::OutputWorker::getPendingIoSize()
return m_pending ? m_currentIoSize : 0;
}
bool NamedPipe::connectToServer(LPCWSTR pipeName)
// Connect to an existing named pipe.
bool NamedPipe::connectToServer(const std::wstring &name)
{
ASSERT(isClosed());
HANDLE handle = CreateFileW(pipeName,
m_name = name;
HANDLE handle = CreateFileW(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
trace("connection to [%ls], handle == 0x%x", pipeName, handle);
trace("connection to [%ls], handle == %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);

View File

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

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -27,7 +27,7 @@
#include "../shared/WinptyVersion.h"
const char USAGE[] =
"Usage: %s controlPipeName dataPipeName cols rows\n"
"Usage: %s controlPipeName flags cols rows\n"
"\n"
"Ordinarily, this program is launched by winpty.dll and is not directly\n"
"useful to winpty users. However, it also has options intended for\n"
@ -72,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;
}

View File

@ -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 $@

View File

@ -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': {

View File

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

View File

@ -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 $@

View File

@ -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
View 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 */

View 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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Ryan Prichard
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@ -18,32 +18,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
View 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
View 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

View 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
View File

@ -0,0 +1,75 @@
// Copyright (c) 2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#ifndef LIBWINPTY_WINPTY_EXCEPTION_H
#define LIBWINPTY_WINPTY_EXCEPTION_H
#include <windows.h>
#include <string>
#include "../include/winpty.h"
#include "../shared/cxx11_noexcept.h"
namespace libwinpty {
#define STATIC_ERROR(name, code, msg) \
static const winpty_error_t name = { true, (code), (msg) };
#define EXTERN_ERROR(name, code, msg) \
extern const winpty_error_t name = { true, (code), (msg) };
extern const winpty_error_t kOutOfMemory;
class WinptyException {
winpty_error_ptr_t m_error;
public:
WinptyException(winpty_result_t code, const std::wstring &msg);
WinptyException(winpty_error_ptr_t error) : m_error(error) {}
~WinptyException() {
if (m_error != nullptr) {
winpty_error_free(m_error);
}
}
winpty_error_ptr_t release() WINPTY_NOEXCEPT {
winpty_error_ptr_t ret = m_error;
m_error = nullptr;
return ret;
}
WinptyException &operator=(const WinptyException &other) = delete;
WinptyException &operator=(WinptyException &&other) = delete;
WinptyException(const WinptyException &other) WINPTY_NOEXCEPT;
WinptyException(WinptyException &&other) WINPTY_NOEXCEPT
: m_error(other.release()) {}
};
void throwStaticError(const winpty_error_t &error);
void throwWinptyException(winpty_result_t code, const std::wstring &msg);
void throwWinptyException(winpty_result_t code, const wchar_t *msg);
void throwLastWindowsError(const std::wstring &prefix);
void throwLastWindowsError(const wchar_t *prefix);
} // libwinpty namespace
#endif // LIBWINPTY_WINPTY_EXCEPTION_H

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

@ -0,0 +1,66 @@
// Copyright (c) 2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#ifndef LIBWINPTY_WINPTY_INTERNAL_H
#define LIBWINPTY_WINPTY_INTERNAL_H
#include <windows.h>
#include <string>
#include "../include/winpty.h"
#include "../shared/cxx11_mutex.h"
#include "Util.h"
// The structures in this header are not intended to be accessed directly by
// client programs.
struct winpty_error_s {
bool errorIsStatic;
winpty_result_t code;
LPCWSTR msg;
};
struct winpty_config_s {
DWORD flags = 0;
int cols = 80;
int rows = 25;
DWORD timeoutMs = 30000;
};
struct winpty_s {
winpty_cxx11::mutex mutex;
libwinpty::OwnedHandle agentProcess;
libwinpty::OwnedHandle controlPipe;
DWORD agentTimeoutMs = 0;
libwinpty::OwnedHandle ioEvent;
std::wstring coninPipeName;
std::wstring conoutPipeName;
};
struct winpty_spawn_config_s {
DWORD winptyFlags = 0;
std::wstring appname;
std::wstring cmdline;
std::wstring cwd;
std::wstring env;
};
#endif // LIBWINPTY_WINPTY_INTERNAL_H

View File

@ -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 $@

View File

@ -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;
}

View File

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

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

@ -0,0 +1,116 @@
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#include "Buffer.h"
#include <assert.h>
#include <stdint.h>
#include "DebugClient.h"
// Define the READ_BUFFER_CHECK() macro. It *must* evaluate its condition,
// exactly once.
#if WINPTY_COMPILER_HAS_EXCEPTIONS
#define THROW_DECODE_ERROR() do { throw DecodeError(); } while (false)
#else
#define THROW_DECODE_ERROR() do { abort(); } while (false)
#endif
#define READ_BUFFER_CHECK(cond) \
do { \
if (!(cond)) { \
if (m_exceptMode == Throw) { \
trace("decode error: %s", #cond); \
THROW_DECODE_ERROR(); \
} else { \
trace("decode error: %s (aborting)", #cond); \
abort(); \
} \
} \
} while (false)
enum class Piece : uint8_t { Int32, Int64, WString };
void WriteBuffer::putRawData(const void *data, size_t len) {
const auto p = reinterpret_cast<const char*>(data);
m_buf.insert(m_buf.end(), p, p + len);
}
void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) {
assert(pos + len <= m_buf.size());
const auto p = reinterpret_cast<const char*>(data);
std::copy(p, p + len, m_buf.begin());
}
void WriteBuffer::putInt32(int32_t i) {
putRawValue(Piece::Int32);
putRawValue(i);
}
void WriteBuffer::putInt64(int64_t i) {
putRawValue(Piece::Int64);
putRawValue(i);
}
// len is in characters, excluding NUL, i.e. the number of wchar_t elements
void WriteBuffer::putWString(const wchar_t *str, size_t len) {
putRawValue(Piece::WString);
putRawValue(static_cast<uint64_t>(len));
putRawData(str, sizeof(wchar_t) * len);
}
void ReadBuffer::getRawData(void *data, size_t len) {
READ_BUFFER_CHECK(m_off + len <= m_buf.size());
const char *const inp = &m_buf[m_off];
std::copy(inp, inp + len, reinterpret_cast<char*>(data));
m_off += len;
}
int32_t ReadBuffer::getInt32() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int32);
return getRawValue<int32_t>();
}
int64_t ReadBuffer::getInt64() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int64);
return getRawValue<int64_t>();
}
std::wstring ReadBuffer::getWString() {
READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::WString);
const size_t charLen = getRawValue<uint64_t>();
const size_t byteLen = charLen * sizeof(wchar_t);
READ_BUFFER_CHECK(m_off + byteLen <= m_buf.size());
// To be strictly conforming, we can't use the convenient wstring
// constructor, because the string in m_buf mightn't be aligned.
std::wstring ret;
if (charLen > 0) {
ret.resize(charLen);
const char *const inp = &m_buf[m_off];
const auto outp = reinterpret_cast<char*>(&ret[0]);
std::copy(inp, inp + byteLen, outp);
m_off += byteLen;
}
return ret;
}
void ReadBuffer::assertEof() {
READ_BUFFER_CHECK(m_off == m_buf.size());
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2011-2012 Ryan Prichard
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@ -21,79 +21,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 */

View File

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

View File

@ -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
View 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
View 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

View File

@ -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;
}

View 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
View 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
View 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
View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,58 @@
// Copyright (c) 2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// Recent 4.x MinGW and MinGW-w64 gcc compilers lack std::mutex and
// std::lock_guard. I have a 5.2.0 MinGW-w64 compiler packaged through MSYS2
// that *is* new enough, but that's one compiler against several deficient
// ones. Wrap CRITICAL_SECTION instead.
#ifndef WINPTY_CXX11_MUTEX_H
#define WINPTY_CXX11_MUTEX_H
#include <windows.h>
namespace winpty_cxx11 {
class mutex {
CRITICAL_SECTION m_mutex;
public:
mutex() { InitializeCriticalSection(&m_mutex); }
~mutex() { DeleteCriticalSection(&m_mutex); }
void lock() { EnterCriticalSection(&m_mutex); }
void unlock() { LeaveCriticalSection(&m_mutex); }
mutex(const mutex &other) = delete;
mutex &operator=(const mutex &other) = delete;
};
template <typename T>
class lock_guard {
T &m_lock;
public:
lock_guard(T &lock) : m_lock(lock) { m_lock.lock(); }
~lock_guard() { m_lock.unlock(); }
lock_guard(const lock_guard &other) = delete;
lock_guard &operator=(const lock_guard &other) = delete;
};
} // winpty_cxx11 namespace
#endif // WINPTY_CXX11_MUTEX_H

19
src/agent/Coord.cc → src/shared/cxx11_noexcept.h Normal file → Executable file
View File

@ -1,4 +1,4 @@
// Copyright (c) 2011-2012 Ryan Prichard
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@ -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
View 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__)

View 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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 $@

View File

@ -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',
],
}
],