master
cpp 206 lines 6.87 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 ConsoleService.cpp
8
9 Abstract:
10
11 This file contains the ConsoleService implementation
12
13 --*/
14 #include <precomp.h>
15 #include <WSLCProcessLauncher.h>
16 #include "ConsoleService.h"
17
18 namespace wsl::windows::wslc::services {
19
20 using wsl::windows::common::ClientRunningWSLCProcess;
21 using wsl::windows::common::io::HandleWrapper;
22 using wsl::windows::common::io::MultiHandleWait;
23 using wsl::windows::common::io::OverlappedIOHandle;
24 using wsl::windows::common::io::ReadConsoleHandle;
25 using wsl::windows::common::io::ReadHandle;
26 using wsl::windows::common::io::RelayHandle;
27
28 namespace {
29
30 // Interrupts and joins the stdin-relay worker thread at teardown.
31 //
32 // The worker only exists when stdin is not a character device (any non-FILE_TYPE_CHAR handle, e.g. a
33 // redirected pipe), so this no-ops (not joinable) for a console. When stdin is a non-overlapped
34 // (synchronous) pipe the worker blocks in a ReadFile() that neither the exit event nor CancelIoEx() can
35 // interrupt, so the join() below would hang until stdin is closed -- the bug this guards against.
36 void InterruptAndJoinInputThread(std::thread& inputThread, wil::unique_event& exitEvent)
37 {
38 if (!inputThread.joinable())
39 {
40 return;
41 }
42
43 WI_ASSERT(exitEvent);
44 exitEvent.SetEvent();
45
46 // Overlapped IO will get terminated by SetEvent(). Synchronous IO will not, so we need to cancel it.
47 const auto threadHandle = static_cast<HANDLE>(inputThread.native_handle());
48 DWORD waitResult = WAIT_TIMEOUT;
49 while (waitResult == WAIT_TIMEOUT)
50 {
51 if (!CancelSynchronousIo(threadHandle))
52 {
53 // ERROR_NOT_FOUND means nothing to cancel; any other error is a corrupt handle that shouldn't happen.
54 const auto cancelError = GetLastError();
55 if (cancelError != ERROR_NOT_FOUND)
56 {
57 FAIL_FAST_WIN32(cancelError);
58 }
59 }
60
61 waitResult = WaitForSingleObject(threadHandle, 50);
62 }
63
64 // Anything but WAIT_OBJECT_0 (e.g. WAIT_FAILED) means a corrupt handle that shouldn't happen.
65 FAIL_FAST_LAST_ERROR_IF(waitResult != WAIT_OBJECT_0);
66
67 inputThread.join();
68 }
69
70 } // namespace
71
72 bool ConsoleService::RelayInteractiveTty(wsl::windows::common::ConsoleState& Console, ClientRunningWSLCProcess& Process, HANDLE Tty, bool TriggerRefresh)
73 {
74 // Configure the console for interactive usage.
75 Console.SetInteractiveMode();
76
77 if (TriggerRefresh)
78 {
79 // In the case of an Attach, force a terminal resize to force the tty to refresh its display.
80 // The docker client uses the same trick.
81
82 auto size = Console.GetWindowSize();
83
84 LOG_IF_FAILED(Process.Get().ResizeTty(size.Y + 1, size.X + 1));
85 LOG_IF_FAILED(Process.Get().ResizeTty(size.Y, size.X));
86 }
87
88 wil::unique_event exitEvent;
89 std::thread inputThread;
90
91 auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { InterruptAndJoinInputThread(inputThread, exitEvent); });
92
93 bool detached = false;
94 MultiHandleWait io;
95
96 auto inputHandle = GetStdHandle(STD_INPUT_HANDLE);
97
98 if (GetFileType(inputHandle) == FILE_TYPE_CHAR)
99 {
100 auto updateTerminal = [&Console, &Process]() {
101 const auto windowSize = Console.GetWindowSize();
102 LOG_IF_FAILED(Process.Get().ResizeTty(windowSize.Y, windowSize.X));
103 };
104
105 // TODO: Make this configurable (default to ctrl-p, ctrl-q).
106 std::vector<char> detachSequence{0x10, 0x11};
107
108 auto onDetach = [&detached]() { detached = true; };
109
110 io.AddHandle(
111 std::make_unique<RelayHandle<ReadConsoleHandle>>(inputHandle, Tty, std::move(updateTerminal), detachSequence, std::move(onDetach)),
112 MultiHandleWait::NeedNotComplete);
113 }
114 else
115 {
116 exitEvent.create(wil::EventOptions::ManualReset);
117
118 inputThread = std::thread{[&]() {
119 try
120 {
121 windows::common::relay::InterruptableRelay(inputHandle, Tty, exitEvent.get());
122 }
123 CATCH_LOG();
124 }};
125 }
126
127 io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(Tty, GetStdHandle(STD_OUTPUT_HANDLE)));
128
129 io.Run({});
130
131 return !detached;
132 }
133
134 void ConsoleService::RelayNonTtyProcess(HandleWrapper&& Stdin, HandleWrapper&& Stdout, HandleWrapper&& Stderr)
135 {
136 // Process output is UTF-8.
137 wsl::windows::common::ConsoleState console;
138 console.SetOutputCodePageUtf8();
139
140 windows::common::io::MultiHandleWait io;
141
142 wil::unique_event exitEvent;
143 std::thread inputThread;
144
145 auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { InterruptAndJoinInputThread(inputThread, exitEvent); });
146
147 if (Stdin.IsValid())
148 {
149 auto input = GetStdHandle(STD_INPUT_HANDLE);
150
151 if (GetFileType(input) == FILE_TYPE_CHAR)
152 {
153 io.AddHandle(std::make_unique<RelayHandle<ReadConsoleHandle>>(input, std::move(Stdin)), MultiHandleWait::NeedNotComplete);
154 }
155 else
156 {
157 // Required because ReadFile() blocks if stdin doesn't support overlapped IO.
158 // This can create pipe deadlocks if we get blocked reading stdin while data is available on stdout / stderr.
159 // TODO: Will output CR instead of LF's which can confuse the linux app.
160 // Consider a custom relay logic to fix this.
161 exitEvent.create(wil::EventOptions::ManualReset);
162
163 inputThread = std::thread{[&]() {
164 try
165 {
166 windows::common::relay::InterruptableRelay(GetStdHandle(STD_INPUT_HANDLE), Stdin.Get(), exitEvent.get());
167 }
168 CATCH_LOG();
169
170 Stdin.Reset();
171 }};
172 }
173 }
174
175 io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(std::move(Stdout), GetStdHandle(STD_OUTPUT_HANDLE)));
176 io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(std::move(Stderr), GetStdHandle(STD_ERROR_HANDLE)));
177
178 io.Run({});
179 }
180
181 int ConsoleService::AttachToCurrentConsole(
182 Terminal& terminal, wsl::windows::common::ConsoleState& console, wsl::windows::common::ClientRunningWSLCProcess&& process, bool triggerRefresh)
183 {
184 if (WI_IsFlagSet(process.Flags(), WSLCProcessFlagsTty))
185 {
186 auto tty = process.GetStdHandle(WSLCFDTty);
187 if (!RelayInteractiveTty(console, process, tty.Get(), triggerRefresh))
188 {
189 terminal.Info(L"[detached]\n");
190 return 0;
191 }
192 }
193 else
194 {
195 HandleWrapper stdinHandle;
196 if (WI_IsFlagSet(process.Flags(), WSLCProcessFlagsStdin))
197 {
198 stdinHandle = process.GetStdHandle(WSLCFDStdin);
199 }
200
201 RelayNonTtyProcess(std::move(stdinHandle), process.GetStdHandle(WSLCFDStdout), process.GetStdHandle(WSLCFDStderr));
202 }
203
204 return process.Wait();
205 }
206 } // namespace wsl::windows::wslc::services