diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index fc07e9d2a22..6ddbe8c3f24 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1942,6 +1942,7 @@ REGSTR reingest Relayout RELBINPATH +remoting Remoting renamer renderengine diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index fe8d0bb6fa7..f73f4c62f48 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -98,7 +98,7 @@ - + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index edb05aa7bc3..1c493b7edd7 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -99,7 +99,7 @@ - + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index 34c943b3749..ad4472156e1 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -99,7 +99,7 @@ - + --> diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index c563adc6b6f..12197c8bc85 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -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(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; diff --git a/src/host/exe/CConsoleHandoff.cpp b/src/host/exe/CConsoleHandoff.cpp index 20ac3c6e806..eaf28088a22 100644 --- a/src/host/exe/CConsoleHandoff.cpp +++ b/src/host/exe/CConsoleHandoff.cpp @@ -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; } diff --git a/src/host/exe/CConsoleHandoff.h b/src/host/exe/CConsoleHandoff.h index e2d2e14d890..36ad564f620 100644 --- a/src/host/exe/CConsoleHandoff.h +++ b/src/host/exe/CConsoleHandoff.h @@ -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 }; diff --git a/src/host/globals.h b/src/host/globals.h index c99794845fa..a955e991f87 100644 --- a/src/host/globals.h +++ b/src/host/globals.h @@ -74,6 +74,8 @@ class Globals std::optional handoffConsoleClsid; std::optional handoffTerminalClsid; + wil::unique_hfile handoffInboxConsoleHandle; + wil::unique_threadpool_wait handoffInboxConsoleExitWait; #ifdef UNIT_TESTING void EnableConptyModeForTests(std::unique_ptr vtRenderEngine); diff --git a/src/host/proxy/IConsoleHandoff.idl b/src/host/proxy/IConsoleHandoff.idl index 067eb0efa83..d7e8d63bc53 100644 --- a/src/host/proxy/IConsoleHandoff.idl +++ b/src/host/proxy/IConsoleHandoff.idl @@ -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); }; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index bebec95b2fd..c1ed6020d33 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -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 remoteControl = std::make_unique(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(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()); diff --git a/src/host/srvinit.h b/src/host/srvinit.h index 3b2725a390e..6fac6771897 100644 --- a/src/host/srvinit.h +++ b/src/host/srvinit.h @@ -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(); diff --git a/src/inc/HostSignals.hpp b/src/inc/HostSignals.hpp new file mode 100644 index 00000000000..05e1d0f3f72 --- /dev/null +++ b/src/inc/HostSignals.hpp @@ -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; + }; +}; \ No newline at end of file diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 996f68d247d..b19b42f16a6 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -53,6 +53,7 @@ // WIL #include #include +#include #include #include #include diff --git a/src/interactivity/base/HostSignalInputThread.cpp b/src/interactivity/base/HostSignalInputThread.cpp new file mode 100644 index 00000000000..2dc71ccca20 --- /dev/null +++ b/src/interactivity/base/HostSignalInputThread.cpp @@ -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(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 +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(); + + LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(msg.processId)); + + break; + } + case HostSignals::SetForeground: + { + auto msg = _ReceiveTypedPacket(); + + LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(ULongToHandle(msg.processId), msg.isForeground)); + + break; + } + case HostSignals::EndTask: + { + auto msg = _ReceiveTypedPacket(); + + 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 buffer; + + while (byteCount > 0) + { + const auto advance = std::min(byteCount, gsl::narrow_cast(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 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(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: +// - +// Return Value: +// - +void HostSignalInputThread::_Shutdown() +{ + // Make sure we terminate. + ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE); +} diff --git a/src/interactivity/base/HostSignalInputThread.hpp b/src/interactivity/base/HostSignalInputThread.hpp new file mode 100644 index 00000000000..429e492955b --- /dev/null +++ b/src/interactivity/base/HostSignalInputThread.hpp @@ -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 + T _ReceiveTypedPacket(); + [[nodiscard]] HRESULT _InputThread(); + + bool _GetData(gsl::span buffer); + bool _AdvanceReader(DWORD byteCount); + void _Shutdown(); + + DWORD _dwThreadId; + wil::unique_hfile _hFile; + wil::unique_handle _hThread; + }; +} diff --git a/src/interactivity/base/RemoteConsoleControl.cpp b/src/interactivity/base/RemoteConsoleControl.cpp new file mode 100644 index 00000000000..21b8155a79f --- /dev/null +++ b/src/interactivity/base/RemoteConsoleControl.cpp @@ -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 +[[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_FROM_WIN32(::GetLastError()))); + } + + if (bytesWritten != sizeof(packet)) + { + NT_RETURN_NTSTATUS(static_cast(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 diff --git a/src/interactivity/base/RemoteConsoleControl.hpp b/src/interactivity/base/RemoteConsoleControl.hpp new file mode 100644 index 00000000000..52de8db1eb3 --- /dev/null +++ b/src/interactivity/base/RemoteConsoleControl.hpp @@ -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; + }; +} diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp index 14d088368af..40fad8e9cbf 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -104,6 +104,24 @@ void ServiceLocator::RundownAndExit(const HRESULT hr) #pragma region Set Methods +[[nodiscard]] NTSTATUS ServiceLocator::SetConsoleControlInstance(_In_ std::unique_ptr&& 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; diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj b/src/interactivity/base/lib/InteractivityBase.vcxproj index bf64e6cf45d..0986a133f5a 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj @@ -6,7 +6,7 @@ Base InteractivityBase ConInteractivityBaseLib - StaticLibrary + StaticLibrary @@ -21,11 +21,13 @@ - + + Create + @@ -40,12 +42,14 @@ - + + + - + \ No newline at end of file diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters index 58825ba6f04..b7f9e8e1890 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters @@ -27,9 +27,15 @@ Source Files - - Source Files - + + Source Files + + + Source Files + + + Source Files + @@ -41,9 +47,6 @@ Header Files - - Source Files - Header Files @@ -77,9 +80,18 @@ Header Files - - Header Files - + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/src/interactivity/base/sources.inc b/src/interactivity/base/sources.inc index 4881f791184..9521ea7c0ed 100644 --- a/src/interactivity/base/sources.inc +++ b/src/interactivity/base/sources.inc @@ -43,7 +43,9 @@ SOURCES = \ ..\InteractivityFactory.cpp \ ..\ServiceLocator.cpp \ ..\VtApiRedirection.cpp \ - ..\EventSynthesis.cpp \ + ..\EventSynthesis.cpp \ + ..\RemoteConsoleControl.cpp \ + ..\HostSignalInputThread.cpp \ INCLUDES = \ $(INCLUDES); \ diff --git a/src/interactivity/inc/ServiceLocator.hpp b/src/interactivity/inc/ServiceLocator.hpp index 888855b4a3a..a7b0dc89a86 100644 --- a/src/interactivity/inc/ServiceLocator.hpp +++ b/src/interactivity/inc/ServiceLocator.hpp @@ -39,6 +39,7 @@ namespace Microsoft::Console::Interactivity static IAccessibilityNotifier* LocateAccessibilityNotifier(); + [[nodiscard]] static NTSTATUS SetConsoleControlInstance(_In_ std::unique_ptr&& control); static IConsoleControl* LocateConsoleControl(); template static T* LocateConsoleControl() diff --git a/src/interactivity/win32/ConsoleControl.hpp b/src/interactivity/win32/ConsoleControl.hpp index aa8b10916b9..9a8ab927fc0 100644 --- a/src/interactivity/win32/ConsoleControl.hpp +++ b/src/interactivity/win32/ConsoleControl.hpp @@ -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. diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index a55419c17e1..1d76c6d4c30 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -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(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.