| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | SessionService.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains the SessionService implementation |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "SessionService.h" |
| 17 | #include "ConsoleService.h" |
| 18 | #include "WarningCallback.h" |
| 19 | #include <wslc.h> |
| 20 | #include <WSLCProcessLauncher.h> |
| 21 | |
| 22 | namespace wsl::windows::wslc::services { |
| 23 | using namespace wsl::shared; |
| 24 | using namespace wsl::windows::wslc::models; |
| 25 | namespace wslutil = wsl::windows::common::wslutil; |
| 26 | |
| 27 | static wil::com_ptr<IWSLCSessionManager> CreateSessionManager() |
| 28 | { |
| 29 | wil::com_ptr<IWSLCSessionManager> manager; |
| 30 | THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&manager))); |
| 31 | wsl::windows::common::security::ConfigureForCOMImpersonation(manager.get()); |
| 32 | return manager; |
| 33 | } |
| 34 | |
| 35 | Session SessionService::OpenSessionByName(const wil::com_ptr<IWSLCSessionManager>& manager, LPCWSTR displayName) |
| 36 | { |
| 37 | wil::com_ptr<IWSLCSession> session; |
| 38 | THROW_IF_FAILED(manager->OpenSessionByName(displayName, &session)); |
| 39 | |
| 40 | wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); |
| 41 | return Session(std::move(session)); |
| 42 | } |
| 43 | |
| 44 | Session SessionService::OpenSession(const std::wstring& sessionName) |
| 45 | { |
| 46 | return OpenSessionByName(CreateSessionManager(), sessionName.c_str()); |
| 47 | } |
| 48 | |
| 49 | Session SessionService::OpenDefaultSession() |
| 50 | { |
| 51 | // Null DisplayName = default session, resolved from caller's token by the server. |
| 52 | return OpenSessionByName(CreateSessionManager(), nullptr); |
| 53 | } |
| 54 | |
| 55 | Session SessionService::OpenOrCreateDefaultSession(Terminal& terminal) |
| 56 | { |
| 57 | WarningCallback warningCallback(terminal); |
| 58 | auto manager = CreateSessionManager(); |
| 59 | |
| 60 | // Null Settings = default session with server-determined name and settings. The warning callback |
| 61 | // is consumed during CreateSession (session initialization); it is not retained afterwards. |
| 62 | wil::com_ptr<IWSLCSession> session; |
| 63 | THROW_IF_FAILED(manager->CreateSession(nullptr, WSLCSessionFlagsNone, &warningCallback, &session)); |
| 64 | wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); |
| 65 | |
| 66 | return Session(std::move(session)); |
| 67 | } |
| 68 | |
| 69 | int SessionService::Attach(Terminal& terminal, const Session& session) |
| 70 | { |
| 71 | // Configure console for interactive usage. |
| 72 | wsl::windows::common::ConsoleState console{}; |
| 73 | console.SetInteractiveMode(); |
| 74 | const auto windowSize = console.GetWindowSize(); |
| 75 | |
| 76 | const std::string shell = "/bin/sh"; |
| 77 | |
| 78 | // Launch with terminal fds (PTY). |
| 79 | wsl::windows::common::WSLCProcessLauncher launcher{shell, {shell, "--login"}, {"TERM=xterm-256color"}, WSLCProcessFlagsTty | WSLCProcessFlagsStdin}; |
| 80 | launcher.SetTtySize(windowSize.Y, windowSize.X); |
| 81 | auto process = launcher.Launch(*session.Get()); |
| 82 | auto tty = process.GetStdHandle(WSLCFDTty); |
| 83 | auto updateTerminalSize = [&]() { |
| 84 | const auto windowSize = console.GetWindowSize(); |
| 85 | LOG_IF_FAILED(process.Get().ResizeTty(windowSize.Y, windowSize.X)); |
| 86 | }; |
| 87 | |
| 88 | // Start input relay thread to forward console input to TTY |
| 89 | // Runs in parallel with output relay (main thread) |
| 90 | auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); |
| 91 | std::thread inputThread([&] { |
| 92 | try |
| 93 | { |
| 94 | wsl::windows::common::relay::StandardInputRelay( |
| 95 | GetStdHandle(STD_INPUT_HANDLE), tty.Get(), updateTerminalSize, exitEvent.get()); |
| 96 | } |
| 97 | catch (...) |
| 98 | { |
| 99 | exitEvent.SetEvent(); |
| 100 | } |
| 101 | }); |
| 102 | |
| 103 | auto joinInput = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { |
| 104 | exitEvent.SetEvent(); |
| 105 | if (inputThread.joinable()) |
| 106 | { |
| 107 | inputThread.join(); |
| 108 | } |
| 109 | }); |
| 110 | |
| 111 | // Relay tty output -> console (blocks until output ends). |
| 112 | wsl::windows::common::relay::InterruptableRelay(tty.Get(), GetStdHandle(STD_OUTPUT_HANDLE), exitEvent.get()); |
| 113 | |
| 114 | process.GetExitEvent().wait(); |
| 115 | |
| 116 | auto exitCode = process.GetExitCode(); |
| 117 | |
| 118 | terminal.Output(L"{}\n", wsl::shared::Localization::MessageWslcShellExited(string::MultiByteToWide(shell), static_cast<int>(exitCode))); |
| 119 | |
| 120 | return static_cast<int>(exitCode); |
| 121 | } |
| 122 | |
| 123 | int SessionService::Enter(Terminal& terminal, const std::wstring& storagePath, const std::wstring& displayName) |
| 124 | { |
| 125 | THROW_HR_IF(E_INVALIDARG, storagePath.empty()); |
| 126 | THROW_HR_IF(E_INVALIDARG, displayName.empty()); |
| 127 | |
| 128 | WarningCallback warningCallback(terminal); |
| 129 | auto sessionManager = CreateSessionManager(); |
| 130 | |
| 131 | wil::com_ptr<IWSLCSession> session; |
| 132 | THROW_IF_FAILED(sessionManager->EnterSession(displayName.c_str(), storagePath.c_str(), &warningCallback, &session)); |
| 133 | wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); |
| 134 | terminal.Info(L"{}\n", Localization::MessageWslcCreatedSession(displayName)); |
| 135 | |
| 136 | const std::string shell = "/bin/sh"; |
| 137 | wsl::windows::common::WSLCProcessLauncher launcher{shell, {shell, "--login"}, {"TERM=xterm-256color"}, WSLCProcessFlagsTty | WSLCProcessFlagsStdin}; |
| 138 | |
| 139 | wsl::windows::common::ConsoleState console; |
| 140 | const auto windowSize = console.GetWindowSize(); |
| 141 | launcher.SetTtySize(windowSize.Y, windowSize.X); |
| 142 | |
| 143 | return ConsoleService::AttachToCurrentConsole(terminal, console, launcher.Launch(*session.get())); |
| 144 | } |
| 145 | |
| 146 | WSLCVersion SessionService::ManagerVersion() |
| 147 | { |
| 148 | WSLCVersion version{}; |
| 149 | THROW_IF_FAILED(CreateSessionManager()->GetVersion(&version)); |
| 150 | |
| 151 | return version; |
| 152 | } |
| 153 | |
| 154 | std::vector<SessionInformation> SessionService::List() |
| 155 | { |
| 156 | std::vector<SessionInformation> result; |
| 157 | auto sessionManager = CreateSessionManager(); |
| 158 | |
| 159 | wil::unique_cotaskmem_array_ptr<WSLCSessionListEntry> sessions; |
| 160 | THROW_IF_FAILED(sessionManager->ListSessions(&sessions, sessions.size_address<ULONG>())); |
| 161 | for (size_t i = 0; i < sessions.size(); ++i) |
| 162 | { |
| 163 | const auto& current = sessions[i]; |
| 164 | SessionInformation info{}; |
| 165 | info.CreatorPid = current.CreatorPid; |
| 166 | info.SessionId = current.SessionId; |
| 167 | info.DisplayName = current.DisplayName; |
| 168 | result.emplace_back(info); |
| 169 | } |
| 170 | |
| 171 | return result; |
| 172 | } |
| 173 | |
| 174 | int SessionService::Run(Terminal& terminal, const Session& session, const std::vector<std::string>& arguments) |
| 175 | { |
| 176 | WI_ASSERT(!arguments.empty()); |
| 177 | |
| 178 | // Pass a default $PATH environment for convenience. |
| 179 | const std::vector<std::string> environment{"PATH=/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin"}; |
| 180 | wsl::windows::common::WSLCProcessLauncher launcher{arguments.front(), arguments, environment, WSLCProcessFlagsStdin}; |
| 181 | |
| 182 | auto [result, process, error] = launcher.LaunchNoThrow(*session.Get()); |
| 183 | THROW_HR_WITH_USER_ERROR_IF(result, Localization::MessageWslcFailedToLaunchCommand(arguments.front(), error), FAILED(result) && error != 0); |
| 184 | |
| 185 | THROW_IF_FAILED(result); |
| 186 | |
| 187 | wsl::windows::common::ConsoleState console{}; |
| 188 | return ConsoleService::AttachToCurrentConsole(terminal, console, std::move(process.value())); |
| 189 | } |
| 190 | |
| 191 | int SessionService::TerminateSession(Terminal& terminal, const Session& session) |
| 192 | { |
| 193 | HRESULT hr = session.Get()->Terminate(); |
| 194 | if (FAILED(hr)) |
| 195 | { |
| 196 | auto errorString = wsl::windows::common::wslutil::ErrorCodeToString(hr); |
| 197 | |
| 198 | wil::unique_cotaskmem_string displayName; |
| 199 | if (SUCCEEDED(session.Get()->GetDisplayName(&displayName)) && displayName) |
| 200 | { |
| 201 | terminal.Error(L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.get()), errorString)); |
| 202 | } |
| 203 | else |
| 204 | { |
| 205 | terminal.Error(L"{}\n", Localization::MessageErrorCode(Localization::MessageWslcTerminateDefaultSessionFailed(), errorString)); |
| 206 | } |
| 207 | return 1; |
| 208 | } |
| 209 | |
| 210 | return 0; |
| 211 | } |
| 212 | } // namespace wsl::windows::wslc::services |