master
cpp 245 lines 7.05 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCProcessLauncher.cpp
8
9 Abstract:
10
11 WSLCProcessLauncher implementation.
12
13 --*/
14
15 #include <precomp.h>
16 #include "WSLCProcessLauncher.h"
17
18 using wsl::windows::common::ClientRunningWSLCProcess;
19 using wsl::windows::common::RunningWSLCProcess;
20 using wsl::windows::common::WSLCProcessLauncher;
21
22 WSLCProcessLauncher::WSLCProcessLauncher(
23 const std::string& Executable, const std::vector<std::string>& Arguments, const std::vector<std::string>& Environment, WSLCProcessFlags Flags) :
24 m_executable(Executable), m_arguments(Arguments), m_environment(Environment), m_flags(Flags)
25 {
26 }
27
28 void WSLCProcessLauncher::SetTtySize(ULONG Rows, ULONG Columns)
29 {
30 m_rows = Rows;
31 m_columns = Columns;
32 }
33
34 void WSLCProcessLauncher::SetWorkingDirectory(std::string&& WorkingDirectory)
35 {
36 m_workingDirectory = std::move(WorkingDirectory);
37 }
38
39 void WSLCProcessLauncher::SetDetachKeys(std::string&& DetachKeys)
40 {
41 m_detachKeys = std::move(DetachKeys);
42 }
43
44 void WSLCProcessLauncher::SetUser(std::string&& User)
45 {
46 m_user = std::move(User);
47 }
48
49 std::tuple<WSLCProcessOptions, std::vector<const char*>, std::vector<const char*>> WSLCProcessLauncher::CreateProcessOptions()
50 {
51 std::vector<const char*> commandLine;
52 std::ranges::transform(m_arguments, std::back_inserter(commandLine), [](const std::string& e) { return e.c_str(); });
53
54 std::vector<const char*> environment;
55 std::ranges::transform(m_environment, std::back_inserter(environment), [](const std::string& e) { return e.c_str(); });
56
57 WSLCProcessOptions options{};
58 options.CommandLine = {.Values = commandLine.data(), .Count = static_cast<DWORD>(commandLine.size())};
59 options.Environment = {.Values = environment.data(), .Count = static_cast<DWORD>(environment.size())};
60 options.Flags = m_flags;
61
62 if (!m_workingDirectory.empty())
63 {
64 options.CurrentDirectory = m_workingDirectory.c_str();
65 }
66
67 if (!m_user.empty())
68 {
69 options.User = m_user.c_str();
70 }
71
72 return std::make_tuple(options, std::move(commandLine), std::move(environment));
73 }
74
75 RunningWSLCProcess::RunningWSLCProcess(WSLCProcessFlags Flags) : m_flags(Flags)
76 {
77 }
78
79 WSLCProcessFlags RunningWSLCProcess::Flags() const
80 {
81 return m_flags;
82 }
83
84 int RunningWSLCProcess::GetExitCode()
85 {
86 WSLCProcessState state{};
87 int code{};
88 GetState(&state, &code);
89
90 THROW_HR_IF_MSG(
91 HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
92 state != WslcProcessStateSignalled && state != WslcProcessStateExited,
93 "Process is not exited. State: %i",
94 state);
95
96 return code;
97 }
98
99 WSLCProcessState RunningWSLCProcess::State()
100 {
101 WSLCProcessState state{};
102 int code{};
103 GetState(&state, &code);
104
105 return state;
106 }
107
108 std::string WSLCProcessLauncher::FormatResult(const RunningWSLCProcess::ProcessResult& result)
109 {
110 auto stdOut = result.Output.find(1);
111 auto stdErr = result.Output.find(2);
112
113 return std::format(
114 "{} [{}] exited with: {}. Stdout: '{}', Stderr: '{}'",
115 m_executable,
116 wsl::shared::string::Join(m_arguments, ','),
117 result.Code,
118 stdOut != result.Output.end() ? stdOut->second : "<none>",
119 stdErr != result.Output.end() ? stdErr->second : "<none>");
120 }
121
122 std::string WSLCProcessLauncher::FormatResult(const int code)
123 {
124 return std::format("{} [{}] exited with: {}.", m_executable, wsl::shared::string::Join(m_arguments, ','), code);
125 }
126
127 int RunningWSLCProcess::Wait(DWORD TimeoutMs)
128 {
129 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_TIMEOUT), !GetExitEvent().wait(TimeoutMs));
130 return GetExitCode();
131 }
132
133 RunningWSLCProcess::ProcessResult RunningWSLCProcess::WaitAndCaptureOutput(DWORD TimeoutMs, std::vector<std::unique_ptr<io::OverlappedIOHandle>>&& ExtraHandles)
134 {
135 RunningWSLCProcess::ProcessResult result;
136
137 io::MultiHandleWait io;
138
139 // Add a callback on IO for each std handle.
140
141 auto addHandle = [&](int fd) {
142 result.Output.emplace(fd, std::string{});
143
144 auto stdHandle = GetStdHandle(fd);
145 auto ioCallback = [Index = fd, &result](const gsl::span<char>& Content) {
146 result.Output[Index].insert(result.Output[Index].end(), Content.begin(), Content.end());
147 };
148
149 io.AddHandle(std::make_unique<io::ReadHandle>(std::move(stdHandle), std::move(ioCallback)));
150 };
151
152 if (WI_IsFlagSet(m_flags, WSLCProcessFlagsTty))
153 {
154 addHandle(WSLCFDTty);
155 }
156 else
157 {
158 addHandle(WSLCFDStdout);
159 addHandle(WSLCFDStderr);
160 }
161
162 for (auto& e : ExtraHandles)
163 {
164 io.AddHandle(std::move(e));
165 }
166
167 // Add a callback for when the process exits.
168 auto exitCallback = [&]() { result.Code = GetExitCode(); };
169
170 io.AddHandle(std::make_unique<io::EventHandle>(GetExitEvent(), std::move(exitCallback)));
171
172 io.Run(std::chrono::milliseconds(TimeoutMs));
173
174 return result;
175 }
176
177 std::tuple<HRESULT, std::optional<ClientRunningWSLCProcess>, int> WSLCProcessLauncher::LaunchNoThrow(IWSLCSession& Session)
178 {
179 auto [options, commandLine, env] = CreateProcessOptions();
180
181 wil::com_ptr<IWSLCProcess> process;
182 int error = -1;
183 auto result =
184 Session.CreateRootNamespaceProcess(m_executable.c_str(), &options, m_rows, m_columns, /* AcquireVmLease */ TRUE, &process, &error);
185 if (FAILED(result))
186 {
187 return std::make_tuple(result, std::optional<ClientRunningWSLCProcess>(), error);
188 }
189
190 wsl::windows::common::security::ConfigureForCOMImpersonation(process.get());
191
192 return {S_OK, ClientRunningWSLCProcess{std::move(process), m_flags}, 0};
193 }
194
195 std::tuple<HRESULT, std::optional<ClientRunningWSLCProcess>> WSLCProcessLauncher::LaunchNoThrow(IWSLCContainer& Container)
196 {
197 auto [options, commandLine, env] = CreateProcessOptions();
198
199 wil::com_ptr<IWSLCProcess> process;
200 WSLCProcessStartOptions startOptions{};
201 startOptions.TtyRows = m_rows;
202 startOptions.TtyColumns = m_columns;
203 startOptions.DetachKeys = m_detachKeys.has_value() ? m_detachKeys->c_str() : nullptr;
204
205 auto result = Container.Exec(&options, &startOptions, &process);
206 if (FAILED(result))
207 {
208 return std::make_pair(result, std::optional<ClientRunningWSLCProcess>());
209 }
210
211 wsl::windows::common::security::ConfigureForCOMImpersonation(process.get());
212
213 return {S_OK, ClientRunningWSLCProcess{std::move(process), m_flags}};
214 }
215
216 IWSLCProcess& ClientRunningWSLCProcess::Get()
217 {
218 return *m_process.get();
219 }
220
221 ClientRunningWSLCProcess::ClientRunningWSLCProcess(wil::com_ptr<IWSLCProcess>&& process, WSLCProcessFlags Flags) :
222 RunningWSLCProcess(Flags), m_process(std::move(process))
223 {
224 }
225
226 wsl::windows::common::io::HandleWrapper ClientRunningWSLCProcess::GetStdHandle(int Index)
227 {
228 wslutil::COMOutputHandle handle;
229 THROW_IF_FAILED_MSG(m_process->GetStdHandle(static_cast<WSLCFD>(Index), &handle), "Failed to get handle: %i", Index);
230
231 return handle.Release();
232 }
233
234 wil::unique_event ClientRunningWSLCProcess::GetExitEvent()
235 {
236 wil::unique_event event{};
237 THROW_IF_FAILED(m_process->GetExitEvent(&event));
238
239 return event;
240 }
241
242 void ClientRunningWSLCProcess::GetState(WSLCProcessState* State, int* Code)
243 {
244 THROW_IF_FAILED(m_process->GetState(State, Code));
245 }