master
cpp 582 lines 18.7 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 relay.cpp
8
9 Abstract:
10
11 This file contains function definitions for relay worker thread routines.
12
13 --*/
14
15 #include "precomp.h"
16 #include "relay.hpp"
17 #pragma hdrstop
18
19 using wsl::windows::common::io::InitializeFileOffset;
20 using wsl::windows::common::relay::ScopedMultiRelay;
21 using wsl::windows::common::relay::ScopedRelay;
22
23 std::thread wsl::windows::common::relay::CreateThread(_In_ HANDLE InputHandle, _In_ HANDLE OutputHandle, _In_opt_ HANDLE ExitHandle, _In_ size_t BufferSize)
24 {
25 return std::thread([InputHandle, OutputHandle, ExitHandle, BufferSize]() {
26 try
27 {
28 wsl::windows::common::wslutil::SetThreadDescription(L"IO Relay");
29 InterruptableRelay(InputHandle, OutputHandle, ExitHandle, BufferSize);
30 }
31 CATCH_LOG()
32 });
33 }
34
35 std::thread wsl::windows::common::relay::CreateThread(
36 _In_ wil::unique_handle&& InputHandle, _In_ HANDLE OutputHandle, _In_opt_ HANDLE ExitHandle, _In_ size_t BufferSize)
37 {
38 return std::thread([InputHandle = std::move(InputHandle), OutputHandle, ExitHandle, BufferSize]() {
39 try
40 {
41 wsl::windows::common::wslutil::SetThreadDescription(L"IO Relay");
42 InterruptableRelay(InputHandle.get(), OutputHandle, ExitHandle, BufferSize);
43 }
44 CATCH_LOG()
45 });
46 }
47
48 std::thread wsl::windows::common::relay::CreateThread(
49 _In_ HANDLE InputHandle, _In_ wil::unique_handle&& OutputHandle, _In_opt_ HANDLE ExitHandle, _In_ size_t BufferSize)
50 {
51 return std::thread([InputHandle, OutputHandle = std::move(OutputHandle), ExitHandle, BufferSize]() {
52 try
53 {
54 wsl::windows::common::wslutil::SetThreadDescription(L"IO Relay");
55 InterruptableRelay(InputHandle, OutputHandle.get(), ExitHandle, BufferSize);
56 }
57 CATCH_LOG()
58 });
59 }
60
61 std::thread wsl::windows::common::relay::CreateThread(
62 _In_ wil::unique_handle&& InputHandle, _In_ wil::unique_handle&& OutputHandle, _In_opt_ HANDLE ExitHandle, _In_ size_t BufferSize)
63 {
64 return std::thread([InputHandle = std::move(InputHandle), OutputHandle = std::move(OutputHandle), ExitHandle, BufferSize]() {
65 try
66 {
67 wsl::windows::common::wslutil::SetThreadDescription(L"IO Relay");
68 InterruptableRelay(InputHandle.get(), OutputHandle.get(), ExitHandle, BufferSize);
69 }
70 CATCH_LOG()
71 });
72 }
73
74 DWORD
75 wsl::windows::common::relay::InterruptableRead(
76 _In_ HANDLE InputHandle, _In_ gsl::span<gsl::byte> Buffer, _In_ const std::vector<HANDLE>& ExitHandles, _In_opt_ LPOVERLAPPED Overlapped)
77 {
78 // Initialize an overlapped structure if one was not provided by the caller.
79 OVERLAPPED overlapped = {};
80 wil::unique_event overlappedEvent = {};
81 if (!ARGUMENT_PRESENT(Overlapped))
82 {
83 overlappedEvent.create(wil::EventOptions::ManualReset);
84 overlapped.hEvent = overlappedEvent.get();
85 Overlapped = &overlapped;
86 }
87
88 DWORD bytesRead = 0;
89 if (!ReadFile(InputHandle, Buffer.data(), gsl::narrow_cast<DWORD>(Buffer.size()), &bytesRead, Overlapped))
90 {
91 auto lastError = GetLastError();
92 if ((lastError == ERROR_HANDLE_EOF) || (lastError == ERROR_BROKEN_PIPE) || (lastError == ERROR_OPERATION_ABORTED))
93 {
94 return 0;
95 }
96
97 THROW_LAST_ERROR_IF_MSG(lastError != ERROR_IO_PENDING, "Handle: 0x%p", (void*)InputHandle);
98
99 auto cancelRead = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
100 CancelIoEx(InputHandle, Overlapped);
101 GetOverlappedResult(InputHandle, Overlapped, &bytesRead, TRUE);
102 });
103
104 // Wait for the read to complete, or the client to exit.
105 if (!InterruptableWait(Overlapped->hEvent, ExitHandles))
106 {
107 return 0;
108 }
109
110 if (!GetOverlappedResult(InputHandle, Overlapped, &bytesRead, FALSE))
111 {
112 lastError = GetLastError();
113 if ((lastError == ERROR_HANDLE_EOF) || (lastError == ERROR_BROKEN_PIPE))
114 {
115 return 0;
116 }
117
118 THROW_LAST_ERROR();
119 }
120
121 cancelRead.release();
122 }
123
124 return bytesRead;
125 }
126
127 void wsl::windows::common::relay::InterruptableRelay(_In_ HANDLE InputHandle, _In_opt_ HANDLE OutputHandle, _In_opt_ HANDLE ExitHandle, _In_ size_t BufferSize)
128 {
129 // If the handle file is seekable, make sure to respect the offset.
130 // This is useful in cases when WSL is invoked on an existing file, like: wsl.exe echo foo >> file
131 // See: https://github.com/microsoft/WSL/issues/11799
132
133 LARGE_INTEGER writeOffset = InitializeFileOffset(OutputHandle);
134 LARGE_INTEGER readOffset = InitializeFileOffset(InputHandle);
135
136 std::vector<gsl::byte> buffer(BufferSize);
137 const auto readSpan = gsl::make_span(buffer);
138
139 std::vector<HANDLE> exitHandles;
140 if (ExitHandle)
141 {
142 exitHandles.push_back(ExitHandle);
143 }
144
145 OVERLAPPED overlapped = {0};
146 const wil::unique_event overlappedEvent(wil::EventOptions::ManualReset);
147 overlapped.hEvent = overlappedEvent.get();
148 for (;;)
149 {
150 overlapped.Offset = readOffset.LowPart;
151 overlapped.OffsetHigh = readOffset.HighPart;
152 const auto bytesRead = InterruptableRead(InputHandle, readSpan, exitHandles, &overlapped);
153 if (bytesRead == 0)
154 {
155 break;
156 }
157
158 readOffset.QuadPart += bytesRead;
159
160 if (OutputHandle)
161 {
162 overlapped.Offset = writeOffset.LowPart;
163 overlapped.OffsetHigh = writeOffset.HighPart;
164 auto writeSpan = readSpan.first(bytesRead);
165 const auto bytesWritten = InterruptableWrite(OutputHandle, writeSpan, exitHandles, &overlapped);
166 if (bytesWritten == 0)
167 {
168 break;
169 }
170
171 WI_ASSERT(bytesWritten == bytesRead);
172 }
173
174 writeOffset.QuadPart += bytesRead;
175 }
176 }
177
178 bool wsl::windows::common::relay::InterruptableWait(_In_ HANDLE WaitObject, _In_ const std::vector<HANDLE>& ExitHandles)
179 {
180 // Wait for the object to become signaled or one of the exit handles to be signaled.
181 std::vector<HANDLE> waitObjects{WaitObject};
182 for (const auto& exitHandle : ExitHandles)
183 {
184 waitObjects.push_back(exitHandle);
185 }
186
187 const DWORD waitResult = WaitForMultipleObjects(gsl::narrow_cast<DWORD>(waitObjects.size()), waitObjects.data(), FALSE, INFINITE);
188 if (waitResult != WAIT_OBJECT_0)
189 {
190 if (waitResult > WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + waitObjects.size())
191 {
192 return false;
193 }
194
195 THROW_HR_MSG(E_FAIL, "WaitForMultipleObjects %d", waitResult);
196 }
197
198 return true;
199 }
200
201 DWORD
202 wsl::windows::common::relay::InterruptableWrite(
203 _In_ HANDLE OutputHandle, _In_ gsl::span<const gsl::byte> Buffer, _In_ const std::vector<HANDLE>& ExitHandles, _In_ LPOVERLAPPED Overlapped)
204 {
205 const DWORD bytesToWrite = gsl::narrow_cast<DWORD>(Buffer.size());
206 DWORD bytesWritten = 0;
207 BOOL success = WriteFile(OutputHandle, Buffer.data(), bytesToWrite, &bytesWritten, Overlapped);
208 if (!success)
209 {
210 const auto lastError = GetLastError();
211 if (lastError == ERROR_NO_DATA)
212 {
213 return 0;
214 }
215
216 THROW_LAST_ERROR_IF(lastError != ERROR_IO_PENDING);
217
218 auto cancelWrite = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
219 CancelIoEx(OutputHandle, Overlapped);
220 GetOverlappedResult(OutputHandle, Overlapped, &bytesWritten, TRUE);
221 });
222
223 if (InterruptableWait(Overlapped->hEvent, ExitHandles))
224 {
225 success = GetOverlappedResult(OutputHandle, Overlapped, &bytesWritten, FALSE);
226 if (success)
227 {
228 cancelWrite.release();
229 }
230 }
231 }
232
233 WI_ASSERT(!success || (bytesWritten == bytesToWrite));
234
235 return bytesWritten;
236 }
237
238 void wsl::windows::common::relay::BidirectionalRelay(_In_ HANDLE LeftHandle, _In_ HANDLE RightHandle, _In_ size_t BufferSize, _In_ RelayFlags Flags)
239 {
240 std::vector<gsl::byte> leftBuffer(BufferSize);
241 const auto leftReadSpan = gsl::make_span(leftBuffer);
242 OVERLAPPED leftOverlapped = {0};
243 const wil::unique_event leftOverlappedEvent(wil::EventOptions::None);
244 leftOverlapped.hEvent = leftOverlappedEvent.get();
245 LARGE_INTEGER leftOffset{};
246
247 std::vector<gsl::byte> rightBuffer(BufferSize);
248 const auto rightReadSpan = gsl::make_span(rightBuffer);
249 OVERLAPPED rightOverlapped = {0};
250 const wil::unique_event rightOverlappedEvent(wil::EventOptions::None);
251 rightOverlapped.hEvent = rightOverlappedEvent.get();
252 LARGE_INTEGER rightOffset{};
253
254 bool leftReadPending = false;
255 bool rightReadPending = false;
256 auto cancelReads = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
257 DWORD bytes;
258 if (leftReadPending)
259 {
260 CancelIoEx(LeftHandle, &leftOverlapped);
261 GetOverlappedResult(LeftHandle, &leftOverlapped, &bytes, TRUE);
262 }
263
264 if (rightReadPending)
265 {
266 CancelIoEx(RightHandle, &rightOverlapped);
267 GetOverlappedResult(RightHandle, &rightOverlapped, &bytes, TRUE);
268 }
269 });
270
271 DWORD bytesWritten;
272 const HANDLE waitObjects[] = {leftOverlapped.hEvent, rightOverlapped.hEvent};
273 for (;;)
274 {
275 if ((LeftHandle == nullptr) || (RightHandle == nullptr))
276 {
277 break;
278 }
279
280 DWORD leftBytesRead = 0;
281 if (!leftReadPending && LeftHandle)
282 {
283 if (!ReadFile(LeftHandle, leftReadSpan.data(), gsl::narrow_cast<DWORD>(leftReadSpan.size()), &leftBytesRead, &leftOverlapped))
284 {
285 THROW_LAST_ERROR_IF(GetLastError() != ERROR_IO_PENDING);
286 }
287
288 leftReadPending = true;
289 }
290
291 DWORD rightBytesRead = 0;
292 if (!rightReadPending && RightHandle)
293 {
294 if (!ReadFile(RightHandle, rightReadSpan.data(), gsl::narrow_cast<DWORD>(rightReadSpan.size()), &rightBytesRead, &rightOverlapped))
295 {
296 THROW_LAST_ERROR_IF(GetLastError() != ERROR_IO_PENDING);
297 }
298
299 rightReadPending = true;
300 }
301
302 const DWORD waitResult = WaitForMultipleObjects(RTL_NUMBER_OF(waitObjects), waitObjects, FALSE, INFINITE);
303 if (waitResult == WAIT_OBJECT_0)
304 {
305 LOG_LAST_ERROR_IF_MSG(
306 !GetOverlappedResult(LeftHandle, &leftOverlapped, &leftBytesRead, FALSE), "WSAGetLastError %d", WSAGetLastError());
307
308 leftReadPending = false;
309 if (leftBytesRead == 0)
310 {
311 LeftHandle = nullptr;
312 if (WI_IsFlagSet(Flags, RelayFlags::RightIsSocket))
313 {
314 LOG_LAST_ERROR_IF(shutdown(reinterpret_cast<SOCKET>(RightHandle), SD_SEND) == SOCKET_ERROR);
315 }
316 }
317 else if (RightHandle != nullptr)
318 {
319 auto writeSpan = leftReadSpan.first(leftBytesRead);
320 bytesWritten = InterruptableWrite(RightHandle, writeSpan, {}, &leftOverlapped);
321 if (bytesWritten == 0)
322 {
323 break;
324 }
325
326 leftOffset.QuadPart += leftBytesRead;
327 leftOverlapped.Offset = leftOffset.LowPart;
328 leftOverlapped.OffsetHigh = leftOffset.HighPart;
329 }
330 }
331 else if (waitResult == (WAIT_OBJECT_0 + 1))
332 {
333 LOG_LAST_ERROR_IF_MSG(
334 !GetOverlappedResult(RightHandle, &rightOverlapped, &rightBytesRead, FALSE), "WSAGetLastError %d", WSAGetLastError());
335
336 rightReadPending = false;
337 if (rightBytesRead == 0)
338 {
339 RightHandle = nullptr;
340 if (WI_IsFlagSet(Flags, RelayFlags::LeftIsSocket))
341 {
342 LOG_LAST_ERROR_IF(shutdown(reinterpret_cast<SOCKET>(LeftHandle), SD_SEND) == SOCKET_ERROR);
343 }
344 }
345 else if (LeftHandle != nullptr)
346 {
347 auto writeSpan = rightReadSpan.first(rightBytesRead);
348 bytesWritten = InterruptableWrite(LeftHandle, writeSpan, {}, &rightOverlapped);
349 if (bytesWritten == 0)
350 {
351 break;
352 }
353
354 rightOffset.QuadPart += rightBytesRead;
355 rightOverlapped.Offset = rightOffset.LowPart;
356 rightOverlapped.OffsetHigh = rightOffset.HighPart;
357 }
358 }
359 else
360 {
361 THROW_HR_MSG(E_FAIL, "WaitForMultipleObjects %d", waitResult);
362 }
363 }
364 }
365
366 bool wsl::windows::common::relay::StandardInputRelay(HANDLE ConsoleHandle, HANDLE OutputHandle, std::function<void()>&& UpdateTerminalSize, HANDLE ExitEvent)
367 {
368 try
369 {
370 if (GetFileType(ConsoleHandle) != FILE_TYPE_CHAR)
371 {
372 wsl::windows::common::relay::InterruptableRelay(ConsoleHandle, OutputHandle, ExitEvent);
373 return true;
374 }
375
376 MultiHandleWait io;
377
378 io.AddHandle(std::make_unique<io::RelayHandle<io::ReadConsoleHandle>>(ConsoleHandle, OutputHandle, std::move(UpdateTerminalSize)));
379
380 io.AddHandle(std::make_unique<io::EventHandle>(ExitEvent), MultiHandleWait::CancelOnCompleted | MultiHandleWait::NeedNotComplete);
381 io.Run({});
382
383 return true;
384 }
385 CATCH_LOG();
386
387 return false;
388 }
389
390 void wsl::windows::common::relay::SocketRelay(_In_ SOCKET LeftSocket, _In_ SOCKET RightSocket, _In_ size_t BufferSize)
391 {
392 constexpr RelayFlags flags = RelayFlags::LeftIsSocket | RelayFlags::RightIsSocket;
393 BidirectionalRelay(reinterpret_cast<HANDLE>(LeftSocket), reinterpret_cast<HANDLE>(RightSocket), BufferSize, flags);
394 }
395
396 void ScopedRelay::Sync()
397 {
398 if (m_thread.joinable())
399 {
400 m_thread.join();
401 }
402 }
403
404 ScopedRelay::~ScopedRelay()
405 {
406 try
407 {
408 m_onDestroy();
409 }
410 CATCH_LOG();
411
412 m_exitEvent.SetEvent();
413 Sync();
414 }
415
416 void ScopedRelay::Run(_In_ HANDLE Input, _In_ HANDLE Output, size_t BufferSize) const
417 {
418 wsl::windows::common::wslutil::SetThreadDescription(L"ScopedRelay");
419
420 try
421 {
422 InterruptableRelay(Input, Output, m_exitEvent.get(), BufferSize);
423 }
424 CATCH_LOG();
425 }
426
427 ScopedMultiRelay::ScopedMultiRelay(const std::vector<HANDLE>& Inputs, const TWriteMethod& Write, size_t BufferSize)
428 {
429 m_thread = std::thread{[this, BufferSize = BufferSize, Inputs = std::move(Inputs), Write = std::move(Write)]() {
430 Run(Inputs, Write, BufferSize);
431 }};
432 }
433
434 void ScopedMultiRelay::Sync()
435 {
436 if (m_thread.joinable())
437 {
438 m_thread.join();
439 }
440 }
441
442 ScopedMultiRelay::~ScopedMultiRelay()
443 {
444 m_exitEvent.SetEvent();
445 Sync();
446 }
447
448 void ScopedMultiRelay::Run(const std::vector<HANDLE>& Handles, const TWriteMethod& Write, size_t BufferSize) const
449 try
450 {
451 enum State
452 {
453 Standby,
454 Pending,
455 Eof
456 };
457
458 struct Input
459 {
460 HANDLE Handle;
461 LARGE_INTEGER Offset;
462 std::vector<std::byte> Buffer;
463 wil::unique_event Event{wil::EventOptions::ManualReset};
464 OVERLAPPED Overlapped;
465 State State = Standby;
466
467 Input(Input&&) = default;
468 Input& operator=(Input&&) = default;
469
470 Input(HANDLE Handle, LARGE_INTEGER Offset, size_t BufferSize) : Handle(Handle), Offset(Offset), Buffer(BufferSize)
471 {
472 Overlapped.hEvent = Event.get();
473 }
474
475 ~Input()
476 {
477 // Cancel outstanding IO, if any.
478 if (State == Pending)
479 {
480 CancelIoEx(Handle, &Overlapped);
481 DWORD bytesRead{};
482 GetOverlappedResult(Handle, &Overlapped, &bytesRead, TRUE);
483 }
484 }
485 };
486
487 std::vector<Input> Inputs;
488 for (const auto& e : Handles)
489 {
490 Inputs.emplace_back(e, InitializeFileOffset(e), BufferSize);
491 }
492
493 while (true)
494 {
495 // Exit if all inputs are completed, or if the exit event is set.
496 if (m_exitEvent.is_signaled() || std::all_of(Inputs.begin(), Inputs.end(), [](const auto& e) { return e.State == Eof; }))
497 {
498 return;
499 }
500
501 for (size_t i = 0; i < Inputs.size(); i++)
502 {
503 auto& e = Inputs[i];
504
505 // If a read has been scheduled, check if IO is available.
506 if (e.State == Pending)
507 {
508 if (e.Event.is_signaled())
509 {
510 DWORD Transferred{};
511 if (!GetOverlappedResult(e.Handle, &e.Overlapped, &Transferred, TRUE))
512 {
513 auto lastError = GetLastError();
514 if ((lastError == ERROR_HANDLE_EOF) || (lastError == ERROR_BROKEN_PIPE))
515 {
516 e.State = Eof;
517 continue;
518 }
519
520 THROW_LAST_ERROR_IF(lastError != ERROR_IO_PENDING);
521 }
522
523 // IO is available.
524 Write(i, gsl::make_span(e.Buffer.data(), Transferred));
525
526 // Update input state.
527 e.Offset.QuadPart += Transferred;
528 e.State = Standby;
529 }
530 }
531
532 // If no read is pending, start one.
533 if (e.State == Standby)
534 {
535 e.Event.ResetEvent();
536
537 e.Overlapped.Offset = e.Offset.LowPart;
538 e.Overlapped.OffsetHigh = e.Offset.HighPart;
539
540 DWORD BytesRead{};
541 if (ReadFile(e.Handle, e.Buffer.data(), static_cast<DWORD>(e.Buffer.size()), &BytesRead, &e.Overlapped))
542 {
543 // IO is available.
544 Write(i, gsl::make_span(e.Buffer.data(), BytesRead));
545
546 // Update input state.
547 e.Offset.QuadPart += BytesRead;
548 e.State = Standby;
549 }
550 else
551 {
552 auto lastError = GetLastError();
553 if ((lastError == ERROR_HANDLE_EOF) || (lastError == ERROR_BROKEN_PIPE))
554 {
555 e.State = Eof;
556 continue;
557 }
558
559 THROW_LAST_ERROR_IF(lastError != ERROR_IO_PENDING);
560 e.State = Pending;
561 }
562 }
563 }
564
565 // Only wait if all non-completed inputs have a scheduled ReadFile to avoid a pipe hang.
566 if (std::all_of(Inputs.begin(), Inputs.end(), [](const auto& e) { return e.State == Eof || e.State == Pending; }))
567 {
568 // Wait until a handle is signaled.
569 std::vector<HANDLE> waits{m_exitEvent.get()};
570 for (const auto& e : Inputs)
571 {
572 if (e.State == Pending)
573 {
574 waits.emplace_back(e.Event.get());
575 }
576 }
577
578 THROW_LAST_ERROR_IF(WaitForMultipleObjects(static_cast<DWORD>(waits.size()), waits.data(), false, INFINITE) == WAIT_FAILED);
579 }
580 }
581 }
582 CATCH_LOG()