master
cpp 235 lines 7.1 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 Dmesg.cpp
8
9 Abstract:
10
11 This file contains logic to collect dmesg output.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Dmesg.h"
17
18 using wsl::windows::common::io::EventHandle;
19 using wsl::windows::common::io::HandleWrapper;
20 using wsl::windows::common::io::MultiHandleWait;
21 using wsl::windows::common::io::ReadNamedPipe;
22 using wsl::windows::common::io::WriteHandle;
23 using wsl::windows::common::io::WriteNamedPipe;
24
25 DmesgCollector::DmesgCollector(
26 GUID VmId, HANDLE ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName, wil::unique_handle&& OutputHandle) :
27 m_com1PipeName(Com1PipeName),
28 m_vmExitEvent(ExitEvent),
29 m_outputHandle(std::move(OutputHandle)),
30 m_runtimeId(VmId),
31 m_debugConsole(EnableDebugConsole),
32 m_telemetry(EnableTelemetry)
33 {
34 }
35
36 DmesgCollector::~DmesgCollector()
37 {
38 m_threadExitEvent.SetEvent();
39 if (m_thread.joinable())
40 {
41 m_thread.join();
42 }
43 }
44
45 std::shared_ptr<DmesgCollector> DmesgCollector::Create(
46 GUID VmId, HANDLE ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName, bool EnableEarlyBootConsole, wil::unique_handle&& OutputHandle)
47 {
48 auto dmesgCollector = std::shared_ptr<DmesgCollector>(
49 new DmesgCollector(VmId, ExitEvent, EnableTelemetry, EnableDebugConsole, Com1PipeName, std::move(OutputHandle)));
50
51 dmesgCollector->Start(EnableEarlyBootConsole);
52 return dmesgCollector;
53 }
54
55 std::pair<std::wstring, wil::unique_hfile> DmesgCollector::CreateConsolePipe()
56 {
57 std::wstring pipeName = wsl::windows::common::helpers::GetUniquePipeName();
58 wil::unique_hfile pipe(CreateNamedPipeW(
59 pipeName.c_str(), (PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED), (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT), 1, LX_RELAY_BUFFER_SIZE, LX_RELAY_BUFFER_SIZE, 0, nullptr));
60
61 THROW_LAST_ERROR_IF(!pipe);
62
63 return {std::move(pipeName), std::move(pipe)};
64 }
65
66 void DmesgCollector::Run()
67 try
68 {
69 wsl::windows::common::wslutil::SetThreadDescription(L"Dmesg");
70
71 MultiHandleWait io;
72
73 if (m_earlyConsolePipe)
74 {
75 io.AddHandle(
76 std::make_unique<ReadNamedPipe>(
77 HandleWrapper{std::move(m_earlyConsolePipe)},
78 [this](const gsl::span<char>& Input) { ProcessInput(DmesgCollectorEarlyConsole, Input); }),
79 MultiHandleWait::IgnoreErrors);
80 }
81
82 io.AddHandle(
83 std::make_unique<ReadNamedPipe>(
84 HandleWrapper{std::move(m_virtioConsolePipe)},
85 [this](const gsl::span<char>& Input) { ProcessInput(DmesgCollectorConsole, Input); }),
86 MultiHandleWait::IgnoreErrors);
87
88 if (m_outputHandle)
89 {
90 auto output = std::make_unique<WriteHandle>(
91 HandleWrapper{std::move(m_outputHandle), [this]() { m_outputWrite = nullptr; }}, std::vector<char>{}, false);
92 m_outputWrite = output.get();
93 io.AddHandle(std::move(output), MultiHandleWait::IgnoreErrors);
94 }
95
96 if (m_com1Pipe)
97 {
98 const bool reconnect = m_pipeServer && !m_debugConsole;
99
100 auto com1 = std::make_unique<WriteNamedPipe>(
101 HandleWrapper{std::move(m_com1Pipe), [this]() { m_com1Write = nullptr; }}, reconnect, !m_pipeServer);
102 m_com1Write = com1.get();
103 io.AddHandle(std::move(com1), MultiHandleWait::IgnoreErrors);
104 }
105
106 // The loop runs until either exit event is signaled.
107 io.AddHandle(std::make_unique<EventHandle>(m_threadExitEvent.get()), MultiHandleWait::CancelOnCompleted);
108 io.AddHandle(std::make_unique<EventHandle>(m_vmExitEvent), MultiHandleWait::CancelOnCompleted);
109
110 io.Run({});
111 }
112 CATCH_LOG()
113
114 namespace {
115
116 template <typename TWriter>
117 void Push(TWriter& Writer, const gsl::span<char>& Input, const char* Target)
118 {
119 constexpr size_t c_maxDmesgPendingBytes = 1024 * 1024;
120
121 const auto pending = Writer.PendingBytes();
122
123 // Don't fill the buffer past c_maxDmesgPendingBytes. If full, just drop the bytes with a warning.
124 if (pending + Input.size() > c_maxDmesgPendingBytes)
125 {
126 WSL_LOG(
127 "DmesgOutputDropped",
128 TraceLoggingValue(Target, "target"),
129 TraceLoggingValue(static_cast<uint64_t>(pending), "pendingBytes"));
130
131 return;
132 }
133
134 Writer.Push(Input);
135 }
136
137 } // namespace
138
139 void DmesgCollector::ProcessInput(InputSource Source, const gsl::span<char>& Input)
140 {
141 if (Input.empty())
142 {
143 return;
144 }
145
146 RingBuffer* ringBuffer = nullptr;
147 bool sendToComPipe = m_debugConsole;
148 if (Source == DmesgCollectorEarlyConsole)
149 {
150 if (!m_earlyConsoleTransition)
151 {
152 ringBuffer = &m_dmesgEarlyBuffer;
153 }
154 else
155 {
156 sendToComPipe = !m_debugConsole;
157 }
158 }
159 else
160 {
161 WI_ASSERT(Source == DmesgCollectorConsole);
162 ringBuffer = &m_dmesgBuffer;
163 if (!m_earlyConsoleTransition)
164 {
165 // The transition is because COM1 may have some other purpose after boot, and that data should not be
166 // captured into the dmesg log. Ideally we would flush all bytes for a clean transition, but since the
167 // legacy serial device is essentially one byte at a time, there isn't a clean way to detect this.
168 m_earlyConsoleTransition = true;
169 }
170 }
171
172 if (ringBuffer != nullptr)
173 {
174 std::string_view inputString{Input.data(), Input.size()};
175 ringBuffer->Insert(inputString);
176 if (m_telemetry)
177 {
178 const auto delimiterCount = std::count(inputString.begin(), inputString.end(), '\n');
179 const auto newStrings = ringBuffer->GetLastDelimitedStrings('\n', delimiterCount);
180 for (const auto& logLine : newStrings)
181 {
182 WSL_LOG("GuestLog", TraceLoggingValue(logLine.c_str(), "text"), TraceLoggingValue(m_runtimeId, "vmId"));
183 }
184 }
185 }
186
187 if (sendToComPipe && m_com1Write)
188 {
189 Push(*m_com1Write, Input, "com1");
190 }
191
192 if (m_outputWrite != nullptr)
193 {
194 Push(*m_outputWrite, Input, "output");
195 }
196 }
197
198 void DmesgCollector::Start(bool EnableEarlyBootConsole)
199 {
200 if (!m_com1PipeName.empty())
201 {
202 // Check if the named pipe has already been created
203 m_com1Pipe.reset(CreateFileW(
204 m_com1PipeName.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, nullptr));
205
206 if (!m_com1Pipe)
207 {
208 m_com1Pipe.reset(CreateNamedPipeW(
209 m_com1PipeName.c_str(),
210 (PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED),
211 (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT),
212 1,
213 LX_RELAY_BUFFER_SIZE,
214 LX_RELAY_BUFFER_SIZE,
215 0,
216 nullptr));
217
218 if (m_com1Pipe)
219 {
220 m_pipeServer = true;
221 }
222 }
223
224 THROW_LAST_ERROR_IF(!m_com1Pipe);
225 }
226
227 if (EnableEarlyBootConsole)
228 {
229 std::tie(m_earlyConsoleName, m_earlyConsolePipe) = CreateConsolePipe();
230 }
231
232 std::tie(m_virtioConsoleName, m_virtioConsolePipe) = CreateConsolePipe();
233
234 m_thread = std::thread([this]() { Run(); });
235 }