mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-08 15:21:01 +00:00
Persist inbox conhost; delegate control activities to it via a pipe (#10415)
Persist inbox conhost; delegate control activities to it via a pipe ## PR Checklist * [x] Closes #10194 - WSL Debug Tap doesn't work * [x] Closes #10134 - WSL Parameter is Incorrect * [x] Closes #10413 - Ctrl+C not passed to client * [x] Closes #10414 - Leftover processes on abrupt termination * [x] Might help #10251 - Win+X Powershell sometimes fails to attach * [x] I work here * [x] Manually tested with assorted launch scenarios ## Detailed Description of the Pull Request / Additional comments It turns out that there's a bit of ownership that goes on with the original inbox `conhost.exe` and the operating system/driver. The PID of that original `conhost.exe` is stowed when the initial connection is established and it is verified for several activities. This means that the plan of letting it go completely away and having the `OpenConsole.exe` take over all of its activities must be slightly revised. I have tested the following two alternatives to keeping `conhost.exe` around and they do not work: 1. Replacing the original owner `conhost.exe` with `OpenConsole.exe` - A.) The driver does not allow this. Once the owner is registered, it cannot be replaced. B.) There's no way of updating this information inside the client process space and it is kept there too in the `kernelbase`/`conclnt` data from its initial connection. 2. Attempting to pick up the first packet (to determine headed/headless and other initial connection information that we use to determine whether handoff is appropriate or not) prior to registering any owner at all. - The driver doesn't allow this either. The owner must be registered prior to a packet coming through. Put this mental model in your head: CMD --> Conhost (inbox) --> OpenConsole (WT Package) --> Terminal (WT Package) So since the `conhost.exe` needs to stick around, here's what I'm doing in this PR: - `conhost.exe` in the OS will receive back the `OpenConsole.exe` process handle on a successful handoff and is expected to remain alive until the `OpenConsole.exe` exits. It's now waiting on that before it terminates itself. - `conhost.exe` in the OS will establish a signal channel pipe and listen for control commands from `OpenConsole.exe` in a very similar fashion to how the `ConPTY` signal pipe operates between the Terminal and the PTY (provided by `OpenConsole.exe` in this particular example.) When `OpenConsole.exe` needs to do something that would be verified by the OS and rejected... it will instead signal the original `conhost.exe` to do that thing and it will go through. - `conhost.exe` will give its own handle through to `OpenConsole.exe` so it can monitor its lifetime and cleanup. If the owner is gone, the session should end. - Assorted handle cleanup that was leading to improper exits. I was confused between `.reset()` and `.release()` for some of the `wil::unique_any<T>` handling and it lead to leaked handles. The leaked handles meant that threads weren't aware of the other sides collapsing and wouldn't cleanup/terminate appropriately. How does this fix things? - For the WSL cases... WSL was specifically looking up the owner PID of the console session from the driver. That was the `conhost.exe` PID. If it exits, that PID isn't valid and is recycled. Thus the parameter is incorrect or other inappropriate WSL setup behaviors. - Ctrl+C not passed... this is a signal the operating system rejects from a PID that is not the owner. This is now relayed through the original owner and it works. - Leftover processes... I believe I explained this was both not-enough-monitoring of each others' process lifetimes coupled with mishandling of release/resetting handles and leaking them. - Powershell sometimes fails to attach... my theory on this one is that it's a race that became upset when the `conhost.exe` disappeared while something about Powershell/.NET was still starting, much like the WSL one. I believe now that it is sticking around, it will be fine. Also, this WILL require an OS update to complete improvement of functionality and I have revised the interface ID. This is considered an acceptable breaking change with no mitigation because we said this feature was an alpha preview. ## Validation Steps Performed - Launched WSL with defapp set, it works - Launched WSL with defapp set and the debug tap on, it works and opens in two tabs - Launched CMD, ran ping, did Ctrl+C, it now receives it - Launched Win+X powershell a ton of times. It seems fine now - Launched cmd, powershell, wsl, etc. Killed assorted processes in the chain (client/conhost/openconsole/windowsterminal) and observed in Process Explorer (with a long delta timer so I could see it) that they all successfully tear down now without leftovers.
This commit is contained in:
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@@ -1942,6 +1942,7 @@ REGSTR
|
||||
reingest
|
||||
Relayout
|
||||
RELBINPATH
|
||||
remoting
|
||||
Remoting
|
||||
renamer
|
||||
renderengine
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="DEC4804D-56D1-4F73-9FBE-6828E7C85C56" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="DEC4804D-56D1-4F73-9FBE-6828E7C85C56"/>
|
||||
<com:Interface Id="E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4" ProxyStubClsid="DEC4804D-56D1-4F73-9FBE-6828E7C85C56"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="DEC4804D-56D1-4F73-9FBE-6828E7C85C56"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="1833E661-CC81-4DD0-87C6-C2F74BD39EFA" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="1833E661-CC81-4DD0-87C6-C2F74BD39EFA"/>
|
||||
<com:Interface Id="E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4" ProxyStubClsid="1833E661-CC81-4DD0-87C6-C2F74BD39EFA"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="1833E661-CC81-4DD0-87C6-C2F74BD39EFA"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
<com:Interface Id="E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension> -->
|
||||
|
||||
@@ -26,7 +26,7 @@ using namespace Microsoft::Console::VirtualTerminal;
|
||||
// - Creates the PTY Signal Input Thread.
|
||||
// Arguments:
|
||||
// - hPipe - a handle to the file representing the read end of the VT pipe.
|
||||
PtySignalInputThread::PtySignalInputThread(_In_ wil::unique_hfile hPipe) :
|
||||
PtySignalInputThread::PtySignalInputThread(wil::unique_hfile hPipe) :
|
||||
_hFile{ std::move(hPipe) },
|
||||
_hThread{},
|
||||
_pConApi{ std::make_unique<ConhostInternalGetSet>(ServiceLocator::LocateGlobals().getConsoleInformation()) },
|
||||
@@ -76,7 +76,7 @@ void PtySignalInputThread::ConnectConsole() noexcept
|
||||
// - The ThreadProc for the PTY Signal Input Thread.
|
||||
// Return Value:
|
||||
// - S_OK if the thread runs to completion.
|
||||
// - Otherwise it may cause an application termination another route and never return.
|
||||
// - Otherwise it may cause an application termination and never return.
|
||||
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread()
|
||||
{
|
||||
unsigned short signalId;
|
||||
|
||||
@@ -27,11 +27,18 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out)
|
||||
// - server - Console driver server handle
|
||||
// - inputEvent - Event already established that we signal when new input data is available in case the driver is waiting on us
|
||||
// - msg - Portable attach message containing just enough descriptor payload to get us started in servicing it
|
||||
// - inboxProcess - Handle to the inbox process so we can watch it to see if it disappears on us.
|
||||
// - process - Handle to our process for waiting for us to exit
|
||||
HRESULT CConsoleHandoff::EstablishHandoff(HANDLE server,
|
||||
HANDLE inputEvent,
|
||||
PCCONSOLE_PORTABLE_ATTACH_MSG msg)
|
||||
PCCONSOLE_PORTABLE_ATTACH_MSG msg,
|
||||
HANDLE signalPipe,
|
||||
HANDLE inboxProcess,
|
||||
HANDLE* process)
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, !process);
|
||||
|
||||
// Fill the descriptor portion of a fresh api message with the received data.
|
||||
// The descriptor portion is the "received" packet from the last ask of the driver.
|
||||
// The other portions are unnecessary as they track the other buffer state, error codes,
|
||||
@@ -53,9 +60,20 @@ try
|
||||
// Making our own duplicate copy ensures they hang around in our lifetime.
|
||||
RETURN_IF_FAILED(_duplicateHandle(server, server));
|
||||
RETURN_IF_FAILED(_duplicateHandle(inputEvent, inputEvent));
|
||||
RETURN_IF_FAILED(_duplicateHandle(signalPipe, signalPipe));
|
||||
RETURN_IF_FAILED(_duplicateHandle(inboxProcess, inboxProcess));
|
||||
|
||||
// Now perform the handoff.
|
||||
RETURN_IF_FAILED(ConsoleEstablishHandoff(server, inputEvent, &apiMsg));
|
||||
RETURN_IF_FAILED(ConsoleEstablishHandoff(server, inputEvent, signalPipe, inboxProcess, &apiMsg));
|
||||
|
||||
// Give back a copy of our own process handle to be tracked.
|
||||
RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
|
||||
GetCurrentProcess(),
|
||||
GetCurrentProcess(),
|
||||
process,
|
||||
SYNCHRONIZE,
|
||||
FALSE,
|
||||
0));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ struct __declspec(uuid(__CLSID_CConsoleHandoff))
|
||||
#pragma region IConsoleHandoff
|
||||
STDMETHODIMP EstablishHandoff(HANDLE server,
|
||||
HANDLE inputEvent,
|
||||
PCCONSOLE_PORTABLE_ATTACH_MSG msg);
|
||||
PCCONSOLE_PORTABLE_ATTACH_MSG msg,
|
||||
HANDLE signalPipe,
|
||||
HANDLE inboxProcess,
|
||||
HANDLE* process);
|
||||
|
||||
#pragma endregion
|
||||
};
|
||||
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
|
||||
std::optional<CLSID> handoffConsoleClsid;
|
||||
std::optional<CLSID> handoffTerminalClsid;
|
||||
wil::unique_hfile handoffInboxConsoleHandle;
|
||||
wil::unique_threadpool_wait handoffInboxConsoleExitWait;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine);
|
||||
|
||||
@@ -20,11 +20,14 @@ typedef const CONSOLE_PORTABLE_ATTACH_MSG* PCCONSOLE_PORTABLE_ATTACH_MSG;
|
||||
|
||||
[
|
||||
object,
|
||||
uuid(2B607BC1-43EB-40C3-95AE-2856ADDB7F23)
|
||||
uuid(E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4)
|
||||
] interface IConsoleHandoff : IUnknown
|
||||
{
|
||||
HRESULT EstablishHandoff([in, system_handle(sh_file)] HANDLE server,
|
||||
[in, system_handle(sh_event)] HANDLE inputEvent,
|
||||
[in, ref] PCCONSOLE_PORTABLE_ATTACH_MSG msg);
|
||||
[in, ref] PCCONSOLE_PORTABLE_ATTACH_MSG msg,
|
||||
[in, system_handle(sh_pipe)] HANDLE signalPipe,
|
||||
[in, system_handle(sh_process)] HANDLE inboxProcess,
|
||||
[out, system_handle(sh_process)] HANDLE* process);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../interactivity/base/ApiDetector.hpp"
|
||||
#include "../interactivity/base/RemoteConsoleControl.hpp"
|
||||
|
||||
#include "renderData.hpp"
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
@@ -364,6 +365,8 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server,
|
||||
// from the driver... or an S_OK success.
|
||||
[[nodiscard]] HRESULT ConsoleEstablishHandoff([[maybe_unused]] _In_ HANDLE Server,
|
||||
[[maybe_unused]] HANDLE driverInputEvent,
|
||||
[[maybe_unused]] HANDLE hostSignalPipe,
|
||||
[[maybe_unused]] HANDLE hostProcessHandle,
|
||||
[[maybe_unused]] PCONSOLE_API_MSG connectMessage)
|
||||
try
|
||||
{
|
||||
@@ -388,6 +391,23 @@ try
|
||||
return E_NOT_SET;
|
||||
}
|
||||
|
||||
// Capture handle to the inbox process into a unique handle holder.
|
||||
g.handoffInboxConsoleHandle.reset(hostProcessHandle);
|
||||
|
||||
// Set up a threadpool waiter to shutdown everything if the inbox process disappears.
|
||||
g.handoffInboxConsoleExitWait.reset(CreateThreadpoolWait(
|
||||
[](PTP_CALLBACK_INSTANCE /*callbackInstance*/, PVOID /*context*/, PTP_WAIT /*wait*/, TP_WAIT_RESULT /*waitResult*/) noexcept {
|
||||
ServiceLocator::RundownAndExit(E_APPLICATION_MANAGER_NOT_RUNNING);
|
||||
},
|
||||
nullptr,
|
||||
nullptr));
|
||||
RETURN_LAST_ERROR_IF_NULL(g.handoffInboxConsoleExitWait.get());
|
||||
|
||||
SetThreadpoolWait(g.handoffInboxConsoleExitWait.get(), g.handoffInboxConsoleHandle.get(), nullptr);
|
||||
|
||||
std::unique_ptr<IConsoleControl> remoteControl = std::make_unique<Microsoft::Console::Interactivity::RemoteConsoleControl>(hostSignalPipe);
|
||||
RETURN_IF_NTSTATUS_FAILED(ServiceLocator::SetConsoleControlInstance(std::move(remoteControl)));
|
||||
|
||||
wil::unique_handle signalPipeTheirSide;
|
||||
wil::unique_handle signalPipeOurSide;
|
||||
|
||||
@@ -397,20 +417,11 @@ try
|
||||
wil::unique_handle outPipeTheirSide;
|
||||
wil::unique_handle outPipeOurSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
// Mark inheritable for signal handle when creating. It'll have the same value on the other side.
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeOurSide.addressof(), signalPipeTheirSide.addressof(), nullptr, 0));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(inPipeOurSide.addressof(), inPipeTheirSide.addressof(), nullptr, 0));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(inPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(outPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
|
||||
|
||||
wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast<DWORD>(connectMessage->Descriptor.Process)) };
|
||||
RETURN_LAST_ERROR_IF_NULL(clientProcess.get());
|
||||
@@ -434,9 +445,9 @@ try
|
||||
serverProcess,
|
||||
clientProcess.get()));
|
||||
|
||||
inPipeTheirSide.release();
|
||||
outPipeTheirSide.release();
|
||||
signalPipeTheirSide.release();
|
||||
inPipeTheirSide.reset();
|
||||
outPipeTheirSide.reset();
|
||||
signalPipeTheirSide.reset();
|
||||
|
||||
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --signal {:#x}"), (int64_t)signalPipeOurSide.release());
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
|
||||
|
||||
[[nodiscard]] HRESULT ConsoleEstablishHandoff(_In_ HANDLE Server,
|
||||
HANDLE driverInputEvent,
|
||||
HANDLE hostSignalPipe,
|
||||
HANDLE hostProcessHandle,
|
||||
PCONSOLE_API_MSG connectMessage);
|
||||
|
||||
void ConsoleCheckDebug();
|
||||
|
||||
33
src/inc/HostSignals.hpp
Normal file
33
src/inc/HostSignals.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Microsoft::Console
|
||||
{
|
||||
// These values match the enumeration values of `ControlType` for the `ConsoleControl` class
|
||||
// but are defined here similarly to not pollute other projects.
|
||||
// They don't *have* to be the same values, but matching them seemed to make sense.
|
||||
enum class HostSignals : uint8_t
|
||||
{
|
||||
NotifyApp = 1u,
|
||||
SetForeground = 5u,
|
||||
EndTask = 7u
|
||||
};
|
||||
|
||||
struct HostSignalNotifyAppData
|
||||
{
|
||||
uint32_t sizeInBytes;
|
||||
uint32_t processId;
|
||||
};
|
||||
|
||||
struct HostSignalSetForegroundData
|
||||
{
|
||||
uint32_t sizeInBytes;
|
||||
uint32_t processId;
|
||||
bool isForeground;
|
||||
};
|
||||
|
||||
struct HostSignalEndTaskData
|
||||
{
|
||||
uint32_t sizeInBytes;
|
||||
uint32_t processId;
|
||||
uint32_t eventType;
|
||||
uint32_t ctrlFlags;
|
||||
};
|
||||
};
|
||||
@@ -53,6 +53,7 @@
|
||||
// WIL
|
||||
#include <wil/Common.h>
|
||||
#include <wil/Result.h>
|
||||
#include <wil/nt_result_macros.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/wistd_memory.h>
|
||||
#include <wil/stl.h>
|
||||
|
||||
216
src/interactivity/base/HostSignalInputThread.cpp
Normal file
216
src/interactivity/base/HostSignalInputThread.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "HostSignalInputThread.hpp"
|
||||
#include "../inc/HostSignals.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
|
||||
// Constructor Description:
|
||||
// - Creates the PTY Signal Input Thread.
|
||||
// Arguments:
|
||||
// - hPipe - a handle to the file representing the read end of the Host Signal pipe.
|
||||
HostSignalInputThread::HostSignalInputThread(wil::unique_hfile&& hPipe) :
|
||||
_hFile{ std::move(hPipe) },
|
||||
_hThread{},
|
||||
_dwThreadId{}
|
||||
{
|
||||
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
|
||||
THROW_HR_IF(E_HANDLE, _hFile.get() == nullptr);
|
||||
}
|
||||
|
||||
HostSignalInputThread::~HostSignalInputThread()
|
||||
{
|
||||
// Manually terminate our thread during unittesting. Otherwise, the test
|
||||
// will finish, but TAEF will not manually kill the test.
|
||||
#ifdef UNIT_TESTING
|
||||
TerminateThread(_hThread.get(), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Static function used for initializing an instance's ThreadProc.
|
||||
// Arguments:
|
||||
// - lpParameter - A pointer to the HostSignalInputThread instance that should be called.
|
||||
// Return Value:
|
||||
// - The return value of the underlying instance's _InputThread
|
||||
DWORD WINAPI HostSignalInputThread::StaticThreadProc(LPVOID lpParameter)
|
||||
{
|
||||
HostSignalInputThread* const pInstance = static_cast<HostSignalInputThread*>(lpParameter);
|
||||
return pInstance->_InputThread();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to retrieve a given type T off of the internal
|
||||
// pipe channel and return it.
|
||||
// Return Value:
|
||||
// - A structure filled with the specified data off the byte stream
|
||||
// - EXCEPTIONS may be thrown if the packet size mismatches
|
||||
// or if we fail to read for another reason.
|
||||
template<typename T>
|
||||
T HostSignalInputThread::_ReceiveTypedPacket()
|
||||
{
|
||||
T msg = { 0 };
|
||||
THROW_HR_IF(E_ABORT, !_GetData(gsl::as_writable_bytes(gsl::span{ &msg, 1 })));
|
||||
|
||||
// If the message is smaller than what we expected
|
||||
// then it was malformed and we need to throw.
|
||||
THROW_HR_IF(E_ILLEGAL_METHOD_CALL, msg.sizeInBytes < sizeof(msg));
|
||||
|
||||
// If the message size was stated to be larger, we
|
||||
// want to seek forward to the next message code.
|
||||
// If it's equal, we'll seek forward by 0 and
|
||||
// do nothing.
|
||||
_AdvanceReader(msg.sizeInBytes - sizeof(msg));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - The ThreadProc for the Host Signal Input Thread.
|
||||
// Return Value:
|
||||
// - S_OK if the thread runs to completion.
|
||||
// - Otherwise it may cause an application termination and never return.
|
||||
[[nodiscard]] HRESULT HostSignalInputThread::_InputThread()
|
||||
{
|
||||
HostSignals signalId;
|
||||
|
||||
while (_GetData(gsl::as_writable_bytes(gsl::span{ &signalId, 1 })))
|
||||
{
|
||||
switch (signalId)
|
||||
{
|
||||
case HostSignals::NotifyApp:
|
||||
{
|
||||
auto msg = _ReceiveTypedPacket<HostSignalNotifyAppData>();
|
||||
|
||||
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(msg.processId));
|
||||
|
||||
break;
|
||||
}
|
||||
case HostSignals::SetForeground:
|
||||
{
|
||||
auto msg = _ReceiveTypedPacket<HostSignalSetForegroundData>();
|
||||
|
||||
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(ULongToHandle(msg.processId), msg.isForeground));
|
||||
|
||||
break;
|
||||
}
|
||||
case HostSignals::EndTask:
|
||||
{
|
||||
auto msg = _ReceiveTypedPacket<HostSignalEndTaskData>();
|
||||
|
||||
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->EndTask(ULongToHandle(msg.processId), msg.eventType, msg.ctrlFlags));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
THROW_HR(E_UNEXPECTED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Skips the file stream forward by the specified number of bytes.
|
||||
// Arguments:
|
||||
// - byteCount - Count of bytes to skip forward
|
||||
// Return Value:
|
||||
// - True if we could skip forward successfully. False otherwise.
|
||||
bool HostSignalInputThread::_AdvanceReader(DWORD byteCount)
|
||||
{
|
||||
std::array<gsl::byte, 256> buffer;
|
||||
|
||||
while (byteCount > 0)
|
||||
{
|
||||
const auto advance = std::min(byteCount, gsl::narrow_cast<DWORD>(buffer.max_size()));
|
||||
|
||||
if (!_GetData(buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byteCount -= advance;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
|
||||
// be compromised.
|
||||
// Arguments:
|
||||
// - buffer - Buffer to fill with data.
|
||||
// Return Value:
|
||||
// - True if data was retrieved successfully. False otherwise.
|
||||
bool HostSignalInputThread::_GetData(gsl::span<gsl::byte> buffer)
|
||||
{
|
||||
DWORD bytesRead = 0;
|
||||
// If we failed to read because the terminal broke our pipe (usually due
|
||||
// to dying itself), close gracefully with ERROR_BROKEN_PIPE.
|
||||
// Otherwise throw an exception. ERROR_BROKEN_PIPE is the only case that
|
||||
// we want to gracefully close in.
|
||||
if (FALSE == ReadFile(_hFile.get(), buffer.data(), gsl::narrow_cast<DWORD>(buffer.size()), &bytesRead, nullptr))
|
||||
{
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
THROW_WIN32(lastError);
|
||||
}
|
||||
|
||||
if (bytesRead != buffer.size())
|
||||
{
|
||||
_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Starts the PTY Signal input thread.
|
||||
[[nodiscard]] HRESULT HostSignalInputThread::Start() noexcept
|
||||
{
|
||||
// 0 is the right value, https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503
|
||||
_dwThreadId = 0;
|
||||
|
||||
_hThread.reset(CreateThread(nullptr,
|
||||
0,
|
||||
HostSignalInputThread::StaticThreadProc,
|
||||
this,
|
||||
0,
|
||||
&_dwThreadId));
|
||||
|
||||
RETURN_LAST_ERROR_IF_NULL(_hThread.get());
|
||||
LOG_IF_FAILED(SetThreadDescription(_hThread.get(), L"Host Signal Handler Thread"));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Perform a shutdown of the console. This happens when the signal pipe is
|
||||
// broken, which means either the parent terminal process has died, or they
|
||||
// called ClosePseudoConsole.
|
||||
// CloseConsoleProcessState is not enough by itself - it will disconnect
|
||||
// clients as if the X was pressed, but then we need to actually still die,
|
||||
// so then call RundownAndExit.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void HostSignalInputThread::_Shutdown()
|
||||
{
|
||||
// Make sure we terminate.
|
||||
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE);
|
||||
}
|
||||
48
src/interactivity/base/HostSignalInputThread.hpp
Normal file
48
src/interactivity/base/HostSignalInputThread.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- HostSignalInputThread.hpp
|
||||
|
||||
Abstract:
|
||||
- Defines methods that wrap the thread that will wait for signals
|
||||
from a delegated console host to this "owner" console.
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (miniksa) 10 Jun 2021
|
||||
|
||||
Notes:
|
||||
- Sourced from `PtySignalInputThread`
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
namespace Microsoft::Console
|
||||
{
|
||||
class HostSignalInputThread final
|
||||
{
|
||||
public:
|
||||
HostSignalInputThread(wil::unique_hfile&& hPipe);
|
||||
~HostSignalInputThread();
|
||||
|
||||
[[nodiscard]] HRESULT Start() noexcept;
|
||||
static DWORD WINAPI StaticThreadProc(LPVOID lpParameter);
|
||||
|
||||
// Prevent copying and assignment.
|
||||
HostSignalInputThread(const HostSignalInputThread&) = delete;
|
||||
HostSignalInputThread& operator=(const HostSignalInputThread&) = delete;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
T _ReceiveTypedPacket();
|
||||
[[nodiscard]] HRESULT _InputThread();
|
||||
|
||||
bool _GetData(gsl::span<gsl::byte> buffer);
|
||||
bool _AdvanceReader(DWORD byteCount);
|
||||
void _Shutdown();
|
||||
|
||||
DWORD _dwThreadId;
|
||||
wil::unique_hfile _hFile;
|
||||
wil::unique_handle _hThread;
|
||||
};
|
||||
}
|
||||
79
src/interactivity/base/RemoteConsoleControl.cpp
Normal file
79
src/interactivity/base/RemoteConsoleControl.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "RemoteConsoleControl.hpp"
|
||||
|
||||
#include "../../inc/HostSignals.hpp"
|
||||
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
|
||||
RemoteConsoleControl::RemoteConsoleControl(HANDLE signalPipe) :
|
||||
_pipe{ signalPipe }
|
||||
{
|
||||
}
|
||||
|
||||
#pragma region IConsoleControl Members
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] NTSTATUS _SendTypedPacket(HANDLE pipe, ::Microsoft::Console::HostSignals signalCode, T& payload)
|
||||
{
|
||||
// To ensure it's a happy wire format, pack it tight at 1.
|
||||
#pragma pack(push, 1)
|
||||
struct HostSignalPacket
|
||||
{
|
||||
::Microsoft::Console::HostSignals code;
|
||||
T data;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
HostSignalPacket packet;
|
||||
packet.code = signalCode;
|
||||
packet.data = payload;
|
||||
|
||||
DWORD bytesWritten = 0;
|
||||
if (!WriteFile(pipe, &packet, sizeof(packet), &bytesWritten, nullptr))
|
||||
{
|
||||
NT_RETURN_NTSTATUS(static_cast<NTSTATUS>(NTSTATUS_FROM_WIN32(::GetLastError())));
|
||||
}
|
||||
|
||||
if (bytesWritten != sizeof(packet))
|
||||
{
|
||||
NT_RETURN_NTSTATUS(static_cast<NTSTATUS>(NTSTATUS_FROM_WIN32(E_UNEXPECTED)));
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS RemoteConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId)
|
||||
{
|
||||
HostSignalNotifyAppData data{};
|
||||
data.sizeInBytes = sizeof(data);
|
||||
data.processId = dwProcessId;
|
||||
|
||||
return _SendTypedPacket(_pipe.get(), HostSignals::NotifyApp, data);
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS RemoteConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground)
|
||||
{
|
||||
HostSignalSetForegroundData data{};
|
||||
data.sizeInBytes = sizeof(data);
|
||||
data.processId = HandleToULong(hProcess);
|
||||
data.isForeground = fForeground;
|
||||
|
||||
return _SendTypedPacket(_pipe.get(), HostSignals::SetForeground, data);
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS RemoteConsoleControl::EndTask(_In_ HANDLE hProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags)
|
||||
{
|
||||
HostSignalEndTaskData data{};
|
||||
data.sizeInBytes = sizeof(data);
|
||||
data.processId = HandleToULong(hProcessId);
|
||||
data.eventType = dwEventType;
|
||||
data.ctrlFlags = ulCtrlFlags;
|
||||
|
||||
return _SendTypedPacket(_pipe.get(), HostSignals::EndTask, data);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
33
src/interactivity/base/RemoteConsoleControl.hpp
Normal file
33
src/interactivity/base/RemoteConsoleControl.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- RemoteConsoleControl.hpp
|
||||
|
||||
Abstract:
|
||||
- This module is used for remoting console control calls to a different host owner process.
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (MiNiksa) 10-Jun-2021
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "../inc/IConsoleControl.hpp"
|
||||
|
||||
namespace Microsoft::Console::Interactivity
|
||||
{
|
||||
class RemoteConsoleControl final : public IConsoleControl
|
||||
{
|
||||
public:
|
||||
RemoteConsoleControl(HANDLE signalPipe);
|
||||
|
||||
// IConsoleControl Members
|
||||
[[nodiscard]] NTSTATUS NotifyConsoleApplication(_In_ DWORD dwProcessId);
|
||||
[[nodiscard]] NTSTATUS SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground);
|
||||
[[nodiscard]] NTSTATUS EndTask(_In_ HANDLE hProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags);
|
||||
|
||||
private:
|
||||
wil::unique_handle _pipe;
|
||||
};
|
||||
}
|
||||
@@ -104,6 +104,24 @@ void ServiceLocator::RundownAndExit(const HRESULT hr)
|
||||
|
||||
#pragma region Set Methods
|
||||
|
||||
[[nodiscard]] NTSTATUS ServiceLocator::SetConsoleControlInstance(_In_ std::unique_ptr<IConsoleControl>&& control)
|
||||
{
|
||||
if (s_consoleControl)
|
||||
{
|
||||
NT_RETURN_NTSTATUS(STATUS_INVALID_HANDLE);
|
||||
}
|
||||
else if (!control)
|
||||
{
|
||||
NT_RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_consoleControl = std::move(control);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS ServiceLocator::SetConsoleWindowInstance(_In_ IConsoleWindow* window)
|
||||
{
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Base</RootNamespace>
|
||||
<ProjectName>InteractivityBase</ProjectName>
|
||||
<TargetName>ConInteractivityBaseLib</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
@@ -21,11 +21,13 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\ApiDetector.cpp" />
|
||||
<ClCompile Include="..\EventSynthesis.cpp" />
|
||||
<ClCompile Include="..\EventSynthesis.cpp" />
|
||||
<ClCompile Include="..\HostSignalInputThread.cpp" />
|
||||
<ClCompile Include="..\InteractivityFactory.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\RemoteConsoleControl.cpp" />
|
||||
<ClCompile Include="..\ServiceLocator.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -40,12 +42,14 @@
|
||||
<ClInclude Include="..\..\inc\IWindowMetrics.hpp" />
|
||||
<ClInclude Include="..\..\inc\Module.hpp" />
|
||||
<ClInclude Include="..\..\inc\VtApiRedirection.hpp" />
|
||||
<ClInclude Include="..\..\inc\EventSynthesis.hpp" />
|
||||
<ClInclude Include="..\..\inc\EventSynthesis.hpp" />
|
||||
<ClInclude Include="..\ApiDetector.hpp" />
|
||||
<ClInclude Include="..\HostSignalInputThread.hpp" />
|
||||
<ClInclude Include="..\InteractivityFactory.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\..\inc\ServiceLocator.hpp" />
|
||||
<ClInclude Include="..\RemoteConsoleControl.hpp" />
|
||||
</ItemGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -27,9 +27,15 @@
|
||||
<ClCompile Include="..\ApiDetector.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\EventSynthesis.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\EventSynthesis.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\RemoteConsoleControl.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\HostSignalInputThread.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\InteractivityFactory.hpp">
|
||||
@@ -41,9 +47,6 @@
|
||||
<ClInclude Include="..\..\inc\ServiceLocator.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ApiDetector.hpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\inc\IAccessibilityNotifier.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@@ -77,9 +80,18 @@
|
||||
<ClInclude Include="..\..\inc\VtApiRedirection.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\inc\EventSynthesis.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\inc\EventSynthesis.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\RemoteConsoleControl.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ApiDetector.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\HostSignalInputThread.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
|
||||
@@ -43,7 +43,9 @@ SOURCES = \
|
||||
..\InteractivityFactory.cpp \
|
||||
..\ServiceLocator.cpp \
|
||||
..\VtApiRedirection.cpp \
|
||||
..\EventSynthesis.cpp \
|
||||
..\EventSynthesis.cpp \
|
||||
..\RemoteConsoleControl.cpp \
|
||||
..\HostSignalInputThread.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Microsoft::Console::Interactivity
|
||||
|
||||
static IAccessibilityNotifier* LocateAccessibilityNotifier();
|
||||
|
||||
[[nodiscard]] static NTSTATUS SetConsoleControlInstance(_In_ std::unique_ptr<IConsoleControl>&& control);
|
||||
static IConsoleControl* LocateConsoleControl();
|
||||
template<typename T>
|
||||
static T* LocateConsoleControl()
|
||||
|
||||
@@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- userdpiapi.hpp
|
||||
- ConsoleControl.hpp
|
||||
|
||||
Abstract:
|
||||
- This module is used for abstracting calls to private user32 DLL APIs to break the build system dependency.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "../host/srvinit.h"
|
||||
#include "../host/telemetry.hpp"
|
||||
|
||||
#include "../interactivity/base/HostSignalInputThread.hpp"
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
@@ -296,16 +297,50 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API
|
||||
HANDLE serverHandle;
|
||||
THROW_IF_FAILED(Globals.pDeviceComm->GetServerHandle(&serverHandle));
|
||||
|
||||
wil::unique_hfile signalPipeTheirSide;
|
||||
wil::unique_hfile signalPipeOurSide;
|
||||
|
||||
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeOurSide.addressof(), signalPipeTheirSide.addressof(), nullptr, 0));
|
||||
|
||||
// Give a copy of our own process handle to be tracked.
|
||||
wil::unique_process_handle ourProcess;
|
||||
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
|
||||
GetCurrentProcess(),
|
||||
GetCurrentProcess(),
|
||||
ourProcess.addressof(),
|
||||
SYNCHRONIZE,
|
||||
FALSE,
|
||||
0));
|
||||
|
||||
wil::unique_process_handle clientProcess;
|
||||
|
||||
// Okay, moment of truth! If they say they successfully took it over, we're going to clean up.
|
||||
// If they fail, we'll throw here and it'll log and we'll just start normally.
|
||||
THROW_IF_FAILED(handoff->EstablishHandoff(serverHandle,
|
||||
Globals.hInputEvent.get(),
|
||||
&msg));
|
||||
&msg,
|
||||
signalPipeTheirSide.get(),
|
||||
ourProcess.get(),
|
||||
&clientProcess));
|
||||
|
||||
// Close handles for the things we gave to them
|
||||
signalPipeTheirSide.reset();
|
||||
ourProcess.reset();
|
||||
Globals.hInputEvent.reset();
|
||||
|
||||
// Start a thread to listen for signals from their side that we must relay to the OS.
|
||||
auto hostSignalThread = std::make_unique<Microsoft::Console::HostSignalInputThread>(std::move(signalPipeOurSide));
|
||||
|
||||
// Start it if it was successfully created.
|
||||
THROW_IF_FAILED(hostSignalThread->Start());
|
||||
|
||||
// Unlock in case anything tries to spool down as we exit.
|
||||
UnlockConsole();
|
||||
|
||||
// We've handed off responsibility. Exit process to clean up any outstanding things we have open.
|
||||
// We've handed off responsibility. Wait for child process to exit so we can maintain PID continuity for some clients.
|
||||
WaitForSingleObject(clientProcess.get(), INFINITE);
|
||||
|
||||
// Exit process to clean up any outstanding things we have open.
|
||||
ExitProcess(S_OK);
|
||||
}
|
||||
CATCH_LOG(); // Just log, don't do anything more. We'll move on to launching normally on failure.
|
||||
|
||||
Reference in New Issue
Block a user