-
Notifications
You must be signed in to change notification settings - Fork 8.4k
/
HostSignalInputThread.cpp
214 lines (181 loc) · 6.71 KB
/
HostSignalInputThread.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// 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)
{
const auto 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))
{
auto lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE)
{
_Shutdown();
}
THROW_WIN32(lastError);
}
if (bytesRead != buffer.size())
{
_Shutdown();
}
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);
}