| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | Process.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains the implementation of the WinRT wrapper for the WSLC SDK Process class. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "Process.h" |
| 17 | #include "Streams.h" |
| 18 | #include "Microsoft.WSL.Containers.Process.g.cpp" |
| 19 | |
| 20 | namespace winrt::Microsoft::WSL::Containers::implementation { |
| 21 | |
| 22 | Process::Process(winrt::Microsoft::WSL::Containers::Container const& container, winrt::Microsoft::WSL::Containers::ProcessSettings const& settings) : |
| 23 | Process(settings) |
| 24 | { |
| 25 | m_container = container; |
| 26 | } |
| 27 | |
| 28 | Process::Process(winrt::Microsoft::WSL::Containers::ProcessSettings const& settings) : m_settings(settings) |
| 29 | { |
| 30 | if (m_settings) |
| 31 | { |
| 32 | m_outputMode = GetImplementation(m_settings)->OutputMode(); |
| 33 | ApplyCallbacksToSettings(); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | Process::Process(winrt::Microsoft::WSL::Containers::ProcessOutputMode outputMode) : m_outputMode(outputMode) |
| 38 | { |
| 39 | // No ProcessSettings; used for opened-container paths where output mode is set independently. |
| 40 | // For Event mode, callers are responsible for registering callbacks via |
| 41 | // WslcSetContainerInitProcessIOCallbacks using the OutputCallback/ExitCallback statics. |
| 42 | } |
| 43 | |
| 44 | void Process::ApplyCallbacksToSettings() |
| 45 | { |
| 46 | // Callbacks are only used with OutputMode::Event. |
| 47 | // Stream and Discard modes use the exit event path (StartWaitingForExitAsync). |
| 48 | if (m_outputMode != ProcessOutputMode::Event) |
| 49 | { |
| 50 | return; |
| 51 | } |
| 52 | |
| 53 | auto settingsPtr = GetStructPointer(m_settings); |
| 54 | |
| 55 | WslcProcessCallbacks callbacks = GetEventCallbacks(); |
| 56 | winrt::check_hresult(WslcSetProcessSettingsCallbacks(settingsPtr, &callbacks, this)); |
| 57 | } |
| 58 | |
| 59 | void Process::StartWaitingForExit() |
| 60 | { |
| 61 | m_waitForExitAction = StartWaitingForExitAsync(); |
| 62 | } |
| 63 | |
| 64 | winrt::Windows::Foundation::IAsyncAction Process::StartWaitingForExitAsync() |
| 65 | { |
| 66 | // Event mode uses the exit callback set in ApplyCallbacksToSettings; no need to wait here. |
| 67 | if (m_outputMode == ProcessOutputMode::Event) |
| 68 | { |
| 69 | co_return; |
| 70 | } |
| 71 | |
| 72 | wil::unique_handle exitEventHandle; |
| 73 | winrt::check_hresult(WslcGetProcessExitEvent(ToHandle(), exitEventHandle.put())); |
| 74 | |
| 75 | // Allow the wait to be cancelled even if suspended for resume_on_signal. |
| 76 | auto cancellation = co_await winrt::get_cancellation_token(); |
| 77 | cancellation.enable_propagation(); |
| 78 | |
| 79 | auto weak_this = get_weak(); |
| 80 | co_await winrt::resume_on_signal(exitEventHandle.get()); |
| 81 | |
| 82 | try |
| 83 | { |
| 84 | if (auto strong_this = weak_this.get()) |
| 85 | { |
| 86 | strong_this->m_exitedEvent(strong_this->ExitCode()); |
| 87 | } |
| 88 | } |
| 89 | CATCH_LOG(); |
| 90 | } |
| 91 | |
| 92 | void Process::AttachHandle(WslcProcess handle) |
| 93 | { |
| 94 | if (m_process) |
| 95 | { |
| 96 | throw winrt::hresult_illegal_method_call(L"Process handle has already been attached"); |
| 97 | } |
| 98 | |
| 99 | m_process.reset(handle); |
| 100 | StartWaitingForExit(); |
| 101 | } |
| 102 | |
| 103 | WslcProcessCallbacks Process::GetEventCallbacks() const noexcept |
| 104 | { |
| 105 | WslcProcessCallbacks callbacks{}; |
| 106 | callbacks.onStdOut = OutputCallback; |
| 107 | callbacks.onStdErr = OutputCallback; |
| 108 | callbacks.onExit = ExitCallback; |
| 109 | return callbacks; |
| 110 | } |
| 111 | |
| 112 | ProcessOutputMode Process::OutputMode() |
| 113 | { |
| 114 | return m_outputMode; |
| 115 | } |
| 116 | |
| 117 | void Process::Start() |
| 118 | { |
| 119 | EnsureCanStart(); |
| 120 | |
| 121 | wil::unique_cotaskmem_string errorMessage; |
| 122 | auto hr = WslcCreateContainerProcess(GetHandle(m_container), GetStructPointer(m_settings), m_process.put(), errorMessage.put()); |
| 123 | THROW_MSG_IF_FAILED(hr, errorMessage); |
| 124 | |
| 125 | m_container = nullptr; |
| 126 | m_settings = nullptr; |
| 127 | |
| 128 | StartWaitingForExit(); |
| 129 | } |
| 130 | |
| 131 | void Process::EnsureStarted() const |
| 132 | { |
| 133 | if (!m_process) |
| 134 | { |
| 135 | throw winrt::hresult_illegal_method_call(L"Process has not been started"); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | void Process::EnsureNotStarted() const |
| 140 | { |
| 141 | if (m_process) |
| 142 | { |
| 143 | throw winrt::hresult_illegal_method_call(L"Process has already been started"); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | void Process::EnsureCanStart() const |
| 148 | { |
| 149 | EnsureNotStarted(); |
| 150 | |
| 151 | if (!m_container) |
| 152 | { |
| 153 | throw winrt::hresult_illegal_method_call(L"Start() cannot be called on the init process, it is started by the container"); |
| 154 | } |
| 155 | |
| 156 | auto cmdLine = GetImplementation(m_settings)->CommandLine(); |
| 157 | if (!cmdLine || cmdLine.Size() == 0) |
| 158 | { |
| 159 | throw winrt::hresult_invalid_argument(L"Process requires a non-empty CommandLine to start"); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | uint32_t Process::Pid() |
| 164 | { |
| 165 | uint32_t pid; |
| 166 | winrt::check_hresult(WslcGetProcessPid(ToHandle(), &pid)); |
| 167 | return pid; |
| 168 | } |
| 169 | |
| 170 | winrt::Microsoft::WSL::Containers::ProcessState Process::State() |
| 171 | { |
| 172 | WslcProcessState state; |
| 173 | winrt::check_hresult(WslcGetProcessState(ToHandle(), &state)); |
| 174 | return static_cast<winrt::Microsoft::WSL::Containers::ProcessState>(state); |
| 175 | } |
| 176 | |
| 177 | int32_t Process::ExitCode() |
| 178 | { |
| 179 | int32_t exitCode; |
| 180 | winrt::check_hresult(WslcGetProcessExitCode(ToHandle(), &exitCode)); |
| 181 | return exitCode; |
| 182 | } |
| 183 | |
| 184 | void Process::Signal(winrt::Microsoft::WSL::Containers::Signal const& signal) |
| 185 | { |
| 186 | winrt::check_hresult(WslcSignalProcess(ToHandle(), static_cast<WslcSignal>(signal))); |
| 187 | } |
| 188 | |
| 189 | winrt::Windows::Storage::Streams::IInputStream Process::GetOutputStream(winrt::Microsoft::WSL::Containers::ProcessOutputHandle const& outputHandle) |
| 190 | { |
| 191 | if (m_outputMode != ProcessOutputMode::Stream) |
| 192 | { |
| 193 | throw winrt::hresult_illegal_method_call(L"GetOutputStream requires OutputMode::Stream"); |
| 194 | } |
| 195 | |
| 196 | wil::unique_handle handle; |
| 197 | winrt::check_hresult(WslcGetProcessIOHandle(ToHandle(), static_cast<WslcProcessIOHandle>(outputHandle), handle.put())); |
| 198 | return winrt::make<IOHandleInputStream>(std::move(handle)); |
| 199 | } |
| 200 | |
| 201 | winrt::Windows::Storage::Streams::IOutputStream Process::GetInputStream() |
| 202 | { |
| 203 | wil::unique_handle handle; |
| 204 | winrt::check_hresult(WslcGetProcessIOHandle(ToHandle(), WSLC_PROCESS_IO_HANDLE_STDIN, handle.put())); |
| 205 | return winrt::make<IOHandleOutputStream>(std::move(handle)); |
| 206 | } |
| 207 | |
| 208 | winrt::event_token Process::OutputReceived(winrt::Microsoft::WSL::Containers::ProcessOutputHandler const& handler) |
| 209 | { |
| 210 | if (m_outputMode != ProcessOutputMode::Event) |
| 211 | { |
| 212 | throw winrt::hresult_illegal_method_call(L"OutputReceived requires OutputMode::Event"); |
| 213 | } |
| 214 | |
| 215 | return m_outputReceivedEvent.add(handler); |
| 216 | } |
| 217 | |
| 218 | void Process::OutputReceived(winrt::event_token const& token) noexcept |
| 219 | { |
| 220 | m_outputReceivedEvent.remove(token); |
| 221 | } |
| 222 | |
| 223 | winrt::event_token Process::ErrorReceived(winrt::Microsoft::WSL::Containers::ProcessOutputHandler const& handler) |
| 224 | { |
| 225 | if (m_outputMode != ProcessOutputMode::Event) |
| 226 | { |
| 227 | throw winrt::hresult_illegal_method_call(L"ErrorReceived requires OutputMode::Event"); |
| 228 | } |
| 229 | |
| 230 | return m_errorReceivedEvent.add(handler); |
| 231 | } |
| 232 | |
| 233 | void Process::ErrorReceived(winrt::event_token const& token) noexcept |
| 234 | { |
| 235 | m_errorReceivedEvent.remove(token); |
| 236 | } |
| 237 | |
| 238 | winrt::event_token Process::Exited(winrt::Microsoft::WSL::Containers::ProcessExitHandler const& handler) |
| 239 | { |
| 240 | return m_exitedEvent.add(handler); |
| 241 | } |
| 242 | |
| 243 | void Process::Exited(winrt::event_token const& token) noexcept |
| 244 | { |
| 245 | m_exitedEvent.remove(token); |
| 246 | } |
| 247 | |
| 248 | void CALLBACK Process::OutputCallback(WslcProcessIOHandle ioHandle, _In_reads_bytes_(dataBytes) const BYTE* data, _In_ uint32_t dataBytes, _In_opt_ PVOID context) noexcept |
| 249 | { |
| 250 | try |
| 251 | { |
| 252 | auto process = static_cast<Process*>(context); |
| 253 | |
| 254 | auto& outputEvent = (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) ? process->m_outputReceivedEvent : process->m_errorReceivedEvent; |
| 255 | winrt::array_view<const uint8_t> buffer{data, dataBytes}; |
| 256 | outputEvent(buffer); |
| 257 | } |
| 258 | CATCH_LOG(); |
| 259 | } |
| 260 | |
| 261 | void CALLBACK Process::ExitCallback(INT32 exitCode, _In_opt_ PVOID context) noexcept |
| 262 | { |
| 263 | try |
| 264 | { |
| 265 | auto process = static_cast<Process*>(context); |
| 266 | process->m_exitedEvent(exitCode); |
| 267 | } |
| 268 | CATCH_LOG(); |
| 269 | } |
| 270 | |
| 271 | WslcProcess Process::ToHandle() |
| 272 | { |
| 273 | EnsureStarted(); |
| 274 | return m_process.get(); |
| 275 | } |
| 276 | |
| 277 | void Process::Close() |
| 278 | { |
| 279 | if (m_waitForExitAction) |
| 280 | { |
| 281 | m_waitForExitAction.Cancel(); |
| 282 | m_waitForExitAction = nullptr; |
| 283 | } |
| 284 | |
| 285 | // Methods called after Close() will fail due to EnsureStarted(). |
| 286 | m_process.reset(); |
| 287 | } |
| 288 | |
| 289 | void Process::final_release(std::unique_ptr<Process> self) |
| 290 | { |
| 291 | // Ensure cleanup when refcount drops to zero even if Close() was not called explicitly. |
| 292 | self->Close(); |
| 293 | } |
| 294 | |
| 295 | } // namespace winrt::Microsoft::WSL::Containers::implementation |