| 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 | } |