Implement a special agent invocation for creating background desktops
Calling SetProcessWindowStation in winpty_open isn't thread-safe -- another thread may be doing something with the window station. The call also appears to interfere with the clipboard, which has affected IntelliJ. A previous commit disabled the use of the background desktop for Windows 7 and up, where it seems unnecessary. This commit fixes the issue for XP and Vista by spawning the agent twice in winpty_open: 1. winpty_open first spawns an agent with no attached console, which creates the background desktop and sends its name back on the control pipe. 2. Then it spawns the primary agent invocation as usual. 3. Once the primary agent's control pipe is connected, winty_open allows the first agent to exit. Fixes https://github.com/rprichard/winpty/issues/58
This commit is contained in:
parent
75ad04155f
commit
8015db36e0
@ -2,6 +2,12 @@
|
||||
|
||||
The winpty library has a new API that should be easier for embedding.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* By default, `winpty.dll` avoids calling `SetProcessWindowStation` within
|
||||
the calling process.
|
||||
[#58](https://github.com/rprichard/winpty/issues/58)
|
||||
|
||||
# Version 0.3.0 (2016-05-20)
|
||||
|
||||
User-visible changes:
|
||||
|
84
src/agent/AgentCreateDesktop.cc
Executable file
84
src/agent/AgentCreateDesktop.cc
Executable file
@ -0,0 +1,84 @@
|
||||
// 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 "AgentCreateDesktop.h"
|
||||
|
||||
#include "../shared/BackgroundDesktop.h"
|
||||
#include "../shared/Buffer.h"
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/StringUtil.h"
|
||||
|
||||
#include "EventLoop.h"
|
||||
#include "NamedPipe.h"
|
||||
|
||||
namespace {
|
||||
|
||||
static inline WriteBuffer newPacket() {
|
||||
WriteBuffer packet;
|
||||
packet.putRawValue<uint64_t>(0); // Reserve space for size.
|
||||
return packet;
|
||||
}
|
||||
|
||||
class CreateDesktopLoop : public EventLoop {
|
||||
public:
|
||||
CreateDesktopLoop(LPCWSTR controlPipeName);
|
||||
|
||||
protected:
|
||||
virtual void onPipeIo(NamedPipe &namedPipe);
|
||||
|
||||
private:
|
||||
void writePacket(WriteBuffer &packet);
|
||||
|
||||
BackgroundDesktop m_desktop;
|
||||
NamedPipe &m_pipe;
|
||||
};
|
||||
|
||||
CreateDesktopLoop::CreateDesktopLoop(LPCWSTR controlPipeName) :
|
||||
m_pipe(createNamedPipe()) {
|
||||
m_pipe.connectToServer(controlPipeName, NamedPipe::OpenMode::Duplex);
|
||||
auto packet = newPacket();
|
||||
packet.putWString(m_desktop.desktopName());
|
||||
writePacket(packet);
|
||||
}
|
||||
|
||||
void CreateDesktopLoop::writePacket(WriteBuffer &packet) {
|
||||
const auto &bytes = packet.buf();
|
||||
packet.replaceRawValue<uint64_t>(0, bytes.size());
|
||||
m_pipe.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
void CreateDesktopLoop::onPipeIo(NamedPipe &namedPipe) {
|
||||
if (m_pipe.isClosed()) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void handleCreateDesktop(LPCWSTR controlPipeName) {
|
||||
try {
|
||||
CreateDesktopLoop loop(controlPipeName);
|
||||
loop.run();
|
||||
trace("Agent exiting...");
|
||||
} catch (const WinptyException &e) {
|
||||
trace("handleCreateDesktop: internal error: %s",
|
||||
utf8FromWide(e.what()).c_str());
|
||||
}
|
||||
}
|
28
src/agent/AgentCreateDesktop.h
Executable file
28
src/agent/AgentCreateDesktop.h
Executable file
@ -0,0 +1,28 @@
|
||||
// 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 AGENT_CREATE_DESKTOP_H
|
||||
#define AGENT_CREATE_DESKTOP_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
void handleCreateDesktop(LPCWSTR controlPipeName);
|
||||
|
||||
#endif // AGENT_CREATE_DESKTOP_H
|
@ -23,15 +23,18 @@
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "Agent.h"
|
||||
#include "DebugShowInput.h"
|
||||
#include "../shared/StringUtil.h"
|
||||
#include "../shared/WindowsVersion.h"
|
||||
#include "../shared/WinptyAssert.h"
|
||||
#include "../shared/WinptyVersion.h"
|
||||
|
||||
#include "Agent.h"
|
||||
#include "AgentCreateDesktop.h"
|
||||
#include "DebugShowInput.h"
|
||||
|
||||
const char USAGE[] =
|
||||
"Usage: %ls controlPipeName flags cols rows\n"
|
||||
"Usage: %ls controlPipeName --create-desktop\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"
|
||||
@ -77,8 +80,13 @@ int main() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc == 3 && !wcscmp(argv[2], L"--create-desktop")) {
|
||||
handleCreateDesktop(argv[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc != 5) {
|
||||
fprintf(stderr, USAGE, argv[0], argv[0]);
|
||||
fprintf(stderr, USAGE, argv[0], argv[0], argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ $(eval $(call def_mingw_target,agent,-DWINPTY_AGENT_ASSERT))
|
||||
|
||||
AGENT_OBJECTS = \
|
||||
build/agent/agent/Agent.o \
|
||||
build/agent/agent/AgentCreateDesktop.o \
|
||||
build/agent/agent/ConsoleFont.o \
|
||||
build/agent/agent/ConsoleInput.o \
|
||||
build/agent/agent/ConsoleLine.o \
|
||||
@ -38,6 +39,7 @@ AGENT_OBJECTS = \
|
||||
build/agent/agent/Win32Console.o \
|
||||
build/agent/agent/Win32ConsoleBuffer.o \
|
||||
build/agent/agent/main.o \
|
||||
build/agent/shared/BackgroundDesktop.o \
|
||||
build/agent/shared/Buffer.o \
|
||||
build/agent/shared/DebugClient.o \
|
||||
build/agent/shared/GenRandom.o \
|
||||
|
@ -65,10 +65,24 @@
|
||||
* them. */
|
||||
#define WINPTY_FLAG_COLOR_ESCAPES 0x4ull
|
||||
|
||||
/* 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
|
||||
* spawns a special agent process to create 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 0x8ull
|
||||
|
||||
#define WINPTY_FLAG_MASK (0ull \
|
||||
| WINPTY_FLAG_CONERR \
|
||||
| WINPTY_FLAG_PLAIN_OUTPUT \
|
||||
| WINPTY_FLAG_COLOR_ESCAPES \
|
||||
| WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \
|
||||
)
|
||||
|
||||
|
||||
|
@ -54,6 +54,7 @@ struct winpty_s {
|
||||
OwnedHandle controlPipe;
|
||||
DWORD agentTimeoutMs = 0;
|
||||
OwnedHandle ioEvent;
|
||||
std::wstring spawnDesktopName;
|
||||
std::wstring coninPipeName;
|
||||
std::wstring conoutPipeName;
|
||||
std::wstring conerrPipeName;
|
||||
|
@ -25,6 +25,7 @@ $(eval $(call def_mingw_target,libwinpty,-DCOMPILING_WINPTY_DLL))
|
||||
LIBWINPTY_OBJECTS = \
|
||||
build/libwinpty/libwinpty/AgentLocation.o \
|
||||
build/libwinpty/libwinpty/winpty.o \
|
||||
build/libwinpty/shared/BackgroundDesktop.o \
|
||||
build/libwinpty/shared/Buffer.o \
|
||||
build/libwinpty/shared/DebugClient.o \
|
||||
build/libwinpty/shared/GenRandom.o \
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "../include/winpty.h"
|
||||
|
||||
#include "../shared/AgentMsg.h"
|
||||
#include "../shared/BackgroundDesktop.h"
|
||||
#include "../shared/Buffer.h"
|
||||
#include "../shared/DebugClient.h"
|
||||
#include "../shared/GenRandom.h"
|
||||
@ -394,36 +395,6 @@ static OwnedHandle createEvent() {
|
||||
return OwnedHandle(h);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// For debugging purposes, provide a way to keep the console on the main window
|
||||
// station, visible.
|
||||
static bool shouldShowConsoleWindow() {
|
||||
@ -431,7 +402,7 @@ static bool shouldShowConsoleWindow() {
|
||||
return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0;
|
||||
}
|
||||
|
||||
static bool shouldCreateBackgroundDesktop() {
|
||||
static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) {
|
||||
// Prior to Windows 7, winpty's repeated selection-deselection loop
|
||||
// prevented the user from interacting with their *visible* console
|
||||
// windows, unless we placed the console onto a background desktop.
|
||||
@ -452,61 +423,26 @@ static bool shouldCreateBackgroundDesktop() {
|
||||
// [2] https://github.com/rprichard/winpty/issues/70
|
||||
bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7();
|
||||
const bool force = hasDebugFlag("force_desktop");
|
||||
const bool force_spawn = hasDebugFlag("force_desktop_spawn");
|
||||
const bool force_curproc = hasDebugFlag("force_desktop_curproc");
|
||||
const bool suppress = hasDebugFlag("no_desktop");
|
||||
if (force && suppress) {
|
||||
trace("error: Both the force_desktop and no_desktop flags are set");
|
||||
if (force + force_spawn + force_curproc + suppress > 1) {
|
||||
trace("error: Only one of force_desktop, force_desktop_spawn, "
|
||||
"force_desktop_curproc, and no_desktop may be set");
|
||||
} else if (force) {
|
||||
ret = true;
|
||||
} else if (force_spawn) {
|
||||
ret = true;
|
||||
createUsingAgent = true;
|
||||
} else if (force_curproc) {
|
||||
ret = true;
|
||||
createUsingAgent = false;
|
||||
} else if (suppress) {
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Get a non-interactive window station for the agent.
|
||||
// TODO: review security w.r.t. windowstation and desktop.
|
||||
static BackgroundDesktop setupBackgroundDesktop() {
|
||||
BackgroundDesktop ret;
|
||||
if (shouldCreateBackgroundDesktop()) {
|
||||
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);
|
||||
trace("Created background desktop: %s",
|
||||
utf8FromWide(ret.desktopName).c_str());
|
||||
} 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 bool shouldSpecifyHideFlag() {
|
||||
const bool force = hasDebugFlag("force_sw_hide");
|
||||
const bool suppress = hasDebugFlag("no_sw_hide");
|
||||
@ -522,24 +458,26 @@ static bool shouldSpecifyHideFlag() {
|
||||
}
|
||||
|
||||
static OwnedHandle startAgentProcess(
|
||||
const BackgroundDesktop &desktop,
|
||||
const std::wstring &desktop,
|
||||
const std::wstring &controlPipeName,
|
||||
uint64_t flags, int cols, int rows,
|
||||
const std::wstring ¶ms,
|
||||
DWORD creationFlags,
|
||||
DWORD &agentPid) {
|
||||
const std::wstring exePath = findAgentProgram();
|
||||
const std::wstring cmdline =
|
||||
(WStringBuilder(256)
|
||||
<< L"\"" << exePath << L"\" "
|
||||
<< controlPipeName << L' '
|
||||
<< flags << L' ' << cols << L' ' << rows).str_moved();
|
||||
<< params).str_moved();
|
||||
|
||||
auto cmdlineV = vectorWithNulFromString(cmdline);
|
||||
auto desktopV = vectorWithNulFromString(desktop.desktopName);
|
||||
auto desktopV = vectorWithNulFromString(desktop);
|
||||
|
||||
// Start the agent.
|
||||
STARTUPINFOW sui = {};
|
||||
sui.cb = sizeof(sui);
|
||||
sui.lpDesktop = desktop.station == nullptr ? nullptr : desktopV.data();
|
||||
sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data();
|
||||
|
||||
if (shouldSpecifyHideFlag()) {
|
||||
sui.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
sui.wShowWindow = SW_HIDE;
|
||||
@ -550,7 +488,7 @@ static OwnedHandle startAgentProcess(
|
||||
cmdlineV.data(),
|
||||
nullptr, nullptr,
|
||||
/*bInheritHandles=*/FALSE,
|
||||
/*dwCreationFlags=*/CREATE_NEW_CONSOLE,
|
||||
/*dwCreationFlags=*/creationFlags,
|
||||
nullptr, nullptr,
|
||||
&sui, &pi);
|
||||
if (!success) {
|
||||
@ -590,14 +528,11 @@ static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) {
|
||||
}
|
||||
}
|
||||
|
||||
WINPTY_API winpty_t *
|
||||
winpty_open(const winpty_config_t *cfg,
|
||||
winpty_error_ptr_t *err /*OPTIONAL*/) {
|
||||
API_TRY {
|
||||
ASSERT(cfg != nullptr);
|
||||
dumpWindowsVersion();
|
||||
dumpVersionToTrace();
|
||||
|
||||
static std::unique_ptr<winpty_t>
|
||||
createAgentSession(const winpty_config_t *cfg,
|
||||
const std::wstring &desktop,
|
||||
const std::wstring ¶ms,
|
||||
DWORD creationFlags) {
|
||||
std::unique_ptr<winpty_t> wp(new winpty_t);
|
||||
wp->agentTimeoutMs = cfg->timeoutMs;
|
||||
wp->ioEvent = createEvent();
|
||||
@ -607,25 +542,123 @@ winpty_open(const winpty_config_t *cfg,
|
||||
L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName();
|
||||
wp->controlPipe = createControlPipe(pipeName);
|
||||
|
||||
// Setup a background desktop for the agent.
|
||||
// TODO: Respect WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION for XP/Vista.
|
||||
BackgroundDesktop desktop = setupBackgroundDesktop();
|
||||
|
||||
// Start the agent and connect the control pipe.
|
||||
DWORD agentPid = 0;
|
||||
wp->agentProcess = startAgentProcess(
|
||||
desktop, pipeName, cfg->flags, cfg->cols, cfg->rows,
|
||||
agentPid);
|
||||
desktop, pipeName, params, creationFlags, agentPid);
|
||||
connectControlPipe(*wp.get());
|
||||
verifyPipeClientPid(wp->controlPipe.get(), agentPid);
|
||||
|
||||
return std::move(wp);
|
||||
}
|
||||
|
||||
class AgentDesktop {
|
||||
public:
|
||||
virtual std::wstring name() = 0;
|
||||
virtual ~AgentDesktop() {}
|
||||
};
|
||||
|
||||
class AgentDesktopDirect : public AgentDesktop {
|
||||
public:
|
||||
AgentDesktopDirect(BackgroundDesktop &&desktop) :
|
||||
m_desktop(std::move(desktop))
|
||||
{
|
||||
}
|
||||
std::wstring name() { return m_desktop.desktopName(); }
|
||||
private:
|
||||
BackgroundDesktop m_desktop;
|
||||
};
|
||||
|
||||
class AgentDesktopIndirect : public AgentDesktop {
|
||||
public:
|
||||
AgentDesktopIndirect(std::unique_ptr<winpty_t> &&wp,
|
||||
std::wstring &&desktopName) :
|
||||
m_wp(std::move(wp)),
|
||||
m_desktopName(std::move(desktopName))
|
||||
{
|
||||
}
|
||||
std::wstring name() { return m_desktopName; }
|
||||
private:
|
||||
std::unique_ptr<winpty_t> m_wp;
|
||||
std::wstring m_desktopName;
|
||||
};
|
||||
|
||||
std::unique_ptr<AgentDesktop>
|
||||
setupBackgroundDesktop(const winpty_config_t *cfg) {
|
||||
bool useDesktopAgent =
|
||||
!(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION);
|
||||
const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent);
|
||||
|
||||
if (!useDesktop) {
|
||||
return std::unique_ptr<AgentDesktop>();
|
||||
}
|
||||
|
||||
if (useDesktopAgent) {
|
||||
auto wp = createAgentSession(
|
||||
cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS);
|
||||
|
||||
// Read the desktop name.
|
||||
auto packet = readPacket(*wp.get());
|
||||
auto desktopName = packet.getWString();
|
||||
packet.assertEof();
|
||||
|
||||
if (desktopName.empty()) {
|
||||
return std::unique_ptr<AgentDesktop>();
|
||||
} else {
|
||||
return std::unique_ptr<AgentDesktop>(
|
||||
new AgentDesktopIndirect(std::move(wp),
|
||||
std::move(desktopName)));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
BackgroundDesktop desktop;
|
||||
return std::unique_ptr<AgentDesktop>(new AgentDesktopDirect(
|
||||
std::move(desktop)));
|
||||
} catch (const WinptyException &e) {
|
||||
trace("Error: failed to create background desktop, "
|
||||
"using original desktop instead: %s",
|
||||
utf8FromWide(e.what()).c_str());
|
||||
return std::unique_ptr<AgentDesktop>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WINPTY_API winpty_t *
|
||||
winpty_open(const winpty_config_t *cfg,
|
||||
winpty_error_ptr_t *err /*OPTIONAL*/) {
|
||||
API_TRY {
|
||||
ASSERT(cfg != nullptr);
|
||||
dumpWindowsVersion();
|
||||
dumpVersionToTrace();
|
||||
|
||||
// Setup a background desktop for the agent.
|
||||
auto desktop = setupBackgroundDesktop(cfg);
|
||||
const auto desktopName = desktop ? desktop->name() : std::wstring();
|
||||
|
||||
// Start the primary agent session.
|
||||
const auto params =
|
||||
(WStringBuilder(128)
|
||||
<< cfg->flags << L' '
|
||||
<< cfg->cols << L' '
|
||||
<< cfg->rows).str_moved();
|
||||
auto wp = createAgentSession(cfg, desktopName, params,
|
||||
CREATE_NEW_CONSOLE);
|
||||
|
||||
// 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);
|
||||
//
|
||||
// If we used a separate agent process to create the desktop, we
|
||||
// disconnect from that process here, allowing it to exit.
|
||||
desktop.reset();
|
||||
|
||||
verifyPipeClientPid(wp->controlPipe.get(), agentPid);
|
||||
// If we ran the agent process on a background desktop, then when we
|
||||
// spawn a child process from the agent, it will need to be explicitly
|
||||
// placed back onto the original desktop.
|
||||
if (!desktopName.empty()) {
|
||||
wp->spawnDesktopName = getCurrentDesktopName();
|
||||
}
|
||||
|
||||
// Get the CONIN/CONOUT pipe names.
|
||||
auto packet = readPacket(*wp.get());
|
||||
@ -773,7 +806,7 @@ winpty_spawn(winpty_t *wp,
|
||||
packet.putWString(cfg->cmdline);
|
||||
packet.putWString(cfg->cwd);
|
||||
packet.putWString(cfg->env);
|
||||
packet.putWString(getDesktopFullName());
|
||||
packet.putWString(wp->spawnDesktopName);
|
||||
writePacket(*wp, packet);
|
||||
|
||||
// Receive reply.
|
||||
|
120
src/shared/BackgroundDesktop.cc
Executable file
120
src/shared/BackgroundDesktop.cc
Executable file
@ -0,0 +1,120 @@
|
||||
// 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.
|
||||
|
||||
#include "BackgroundDesktop.h"
|
||||
|
||||
#include "DebugClient.h"
|
||||
#include "StringUtil.h"
|
||||
#include "WinptyException.h"
|
||||
|
||||
namespace {
|
||||
|
||||
static std::wstring getObjectName(HANDLE object) {
|
||||
BOOL success;
|
||||
DWORD lengthNeeded = 0;
|
||||
GetUserObjectInformationW(object, UOI_NAME,
|
||||
nullptr, 0,
|
||||
&lengthNeeded);
|
||||
ASSERT(lengthNeeded % sizeof(wchar_t) == 0);
|
||||
std::unique_ptr<wchar_t[]> tmp(
|
||||
new wchar_t[lengthNeeded / sizeof(wchar_t)]);
|
||||
success = GetUserObjectInformationW(object, UOI_NAME,
|
||||
tmp.get(), lengthNeeded,
|
||||
nullptr);
|
||||
if (!success) {
|
||||
throwWindowsError(L"GetUserObjectInformationW failed");
|
||||
}
|
||||
return std::wstring(tmp.get());
|
||||
}
|
||||
|
||||
static std::wstring getDesktopName(HWINSTA winsta, HDESK desk) {
|
||||
return getObjectName(winsta) + L"\\" + getObjectName(desk);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Get a non-interactive window station for the agent.
|
||||
// TODO: review security w.r.t. windowstation and desktop.
|
||||
BackgroundDesktop::BackgroundDesktop() {
|
||||
try {
|
||||
m_originalStation = GetProcessWindowStation();
|
||||
if (m_originalStation == nullptr) {
|
||||
throwWindowsError(
|
||||
L"BackgroundDesktop ctor: "
|
||||
L"GetProcessWindowStation returned NULL");
|
||||
}
|
||||
m_newStation =
|
||||
CreateWindowStationW(nullptr, 0, WINSTA_ALL_ACCESS, nullptr);
|
||||
if (m_newStation == nullptr) {
|
||||
throwWindowsError(
|
||||
L"BackgroundDesktop ctor: CreateWindowStationW returned NULL");
|
||||
}
|
||||
if (!SetProcessWindowStation(m_newStation)) {
|
||||
throwWindowsError(
|
||||
L"BackgroundDesktop ctor: SetProcessWindowStation failed");
|
||||
}
|
||||
m_newDesktop = CreateDesktopW(
|
||||
L"Default", nullptr, nullptr, 0, GENERIC_ALL, nullptr);
|
||||
if (m_newDesktop == nullptr) {
|
||||
throwWindowsError(
|
||||
L"BackgroundDesktop ctor: CreateDesktopW failed");
|
||||
}
|
||||
m_newDesktopName = getDesktopName(m_newStation, m_newDesktop);
|
||||
trace("Created background desktop: %s",
|
||||
utf8FromWide(m_newDesktopName).c_str());
|
||||
} catch (...) {
|
||||
dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundDesktop::dispose() WINPTY_NOEXCEPT {
|
||||
if (m_originalStation != nullptr) {
|
||||
SetProcessWindowStation(m_originalStation);
|
||||
m_originalStation = nullptr;
|
||||
}
|
||||
if (m_newDesktop != nullptr) {
|
||||
CloseDesktop(m_newDesktop);
|
||||
m_newDesktop = nullptr;
|
||||
}
|
||||
if (m_newStation != nullptr) {
|
||||
CloseWindowStation(m_newStation);
|
||||
m_newStation = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring getCurrentDesktopName() {
|
||||
// MSDN says that the handles returned by GetProcessWindowStation and
|
||||
// GetThreadDesktop do not need to be passed to CloseWindowStation and
|
||||
// CloseDesktop, respectively.
|
||||
const HWINSTA winsta = GetProcessWindowStation();
|
||||
if (winsta == nullptr) {
|
||||
throwWindowsError(
|
||||
L"getCurrentDesktopName: "
|
||||
L"GetProcessWindowStation returned NULL");
|
||||
}
|
||||
const HDESK desk = GetThreadDesktop(GetCurrentThreadId());
|
||||
if (desk == nullptr) {
|
||||
throwWindowsError(
|
||||
L"getCurrentDesktopName: "
|
||||
L"GetThreadDesktop returned NULL");
|
||||
}
|
||||
return getDesktopName(winsta, desk);
|
||||
}
|
73
src/shared/BackgroundDesktop.h
Executable file
73
src/shared/BackgroundDesktop.h
Executable file
@ -0,0 +1,73 @@
|
||||
// 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_SHARED_BACKGROUND_DESKTOP_H
|
||||
#define WINPTY_SHARED_BACKGROUND_DESKTOP_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinptyException.h"
|
||||
|
||||
class BackgroundDesktop {
|
||||
public:
|
||||
BackgroundDesktop();
|
||||
~BackgroundDesktop() { dispose(); }
|
||||
void dispose() WINPTY_NOEXCEPT;
|
||||
const std::wstring &desktopName() const { return m_newDesktopName; }
|
||||
|
||||
BackgroundDesktop(const BackgroundDesktop &other) = delete;
|
||||
BackgroundDesktop &operator=(const BackgroundDesktop &other) = delete;
|
||||
|
||||
// We can't default the move constructor and assignment operator with
|
||||
// MSVC 2013. We *could* if we required at least MSVC 2015 to build.
|
||||
|
||||
BackgroundDesktop(BackgroundDesktop &&other) :
|
||||
m_originalStation(other.m_originalStation),
|
||||
m_newStation(other.m_newStation),
|
||||
m_newDesktop(other.m_newDesktop),
|
||||
m_newDesktopName(std::move(other.m_newDesktopName)) {
|
||||
other.m_originalStation = nullptr;
|
||||
other.m_newStation = nullptr;
|
||||
other.m_newDesktop = nullptr;
|
||||
}
|
||||
BackgroundDesktop &operator=(BackgroundDesktop &&other) {
|
||||
dispose();
|
||||
m_originalStation = other.m_originalStation;
|
||||
m_newStation = other.m_newStation;
|
||||
m_newDesktop = other.m_newDesktop;
|
||||
m_newDesktopName = std::move(other.m_newDesktopName);
|
||||
other.m_originalStation = nullptr;
|
||||
other.m_newStation = nullptr;
|
||||
other.m_newDesktop = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
HWINSTA m_originalStation = nullptr;
|
||||
HWINSTA m_newStation = nullptr;
|
||||
HDESK m_newDesktop = nullptr;
|
||||
std::wstring m_newDesktopName;
|
||||
};
|
||||
|
||||
std::wstring getCurrentDesktopName();
|
||||
|
||||
#endif // WINPTY_SHARED_BACKGROUND_DESKTOP_H
|
@ -509,7 +509,7 @@ int main(int argc, char *argv[])
|
||||
sz.ws_row = 25;
|
||||
ioctl(STDIN_FILENO, TIOCGWINSZ, &sz);
|
||||
|
||||
DWORD agentFlags = 0;
|
||||
DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION;
|
||||
if (args.testConerr) { agentFlags |= WINPTY_FLAG_CONERR; }
|
||||
if (args.testPlainOutput) { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; }
|
||||
if (args.testColorEscapes) { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; }
|
||||
|
@ -52,6 +52,8 @@
|
||||
'sources' : [
|
||||
'agent/Agent.h',
|
||||
'agent/Agent.cc',
|
||||
'agent/AgentCreateDesktop.h',
|
||||
'agent/AgentCreateDesktop.cc',
|
||||
'agent/ConsoleFont.cc',
|
||||
'agent/ConsoleFont.h',
|
||||
'agent/ConsoleInput.cc',
|
||||
@ -85,6 +87,8 @@
|
||||
'agent/Win32ConsoleBuffer.h',
|
||||
'agent/main.cc',
|
||||
'shared/AgentMsg.h',
|
||||
'shared/BackgroundDesktop.h',
|
||||
'shared/BackgroundDesktop.cc',
|
||||
'shared/Buffer.h',
|
||||
'shared/Buffer.cc',
|
||||
'shared/DebugClient.h',
|
||||
@ -137,6 +141,8 @@
|
||||
'libwinpty/AgentLocation.h',
|
||||
'libwinpty/winpty.cc',
|
||||
'shared/AgentMsg.h',
|
||||
'shared/BackgroundDesktop.h',
|
||||
'shared/BackgroundDesktop.cc',
|
||||
'shared/Buffer.h',
|
||||
'shared/Buffer.cc',
|
||||
'shared/DebugClient.h',
|
||||
|
Loading…
Reference in New Issue
Block a user