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