Skip to content

Commit 9d39805

Browse files
authored
Implement wslc session enter (#40088)
* Save state * Save state * Save state * Cleanup * Prepare for PR * Cleanup * Validate flags * Apply PR suggestions * Add comment * Remove include * Cleanup diff * Format * Format * Restore session flags
1 parent 2d8c8cf commit 9d39805

18 files changed

Lines changed: 327 additions & 198 deletions

localization/strings/en-US/Resources.resw

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1939,6 +1939,10 @@ Usage:
19391939
<value>{} exited with: {}</value>
19401940
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
19411941
</data>
1942+
<data name="MessageWslcCreatedSession" xml:space="preserve">
1943+
<value>Created session: '{}'</value>
1944+
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
1945+
</data>
19421946
<data name="MessageWslcHeaderId" xml:space="preserve">
19431947
<value>ID</value>
19441948
</data>
@@ -2114,10 +2118,14 @@ For privacy information about this product please visit https://aka.ms/privacy.<
21142118
<value>Failed to open '{}': {}</value>
21152119
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
21162120
</data>
2117-
<data name = "MessageWslcBuildFileNotFound" xml:space = "preserve" >
2121+
<data name = "MessageWslcBuildFileNotFound" xml:space = "preserve" >
21182122
<value>No Containerfile or Dockerfile found in '{}'</value>
21192123
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
21202124
</data>
2125+
<data name = "MessageWslcSessionStorageNotFound" xml:space = "preserve" >
2126+
<value>No WSLC session found in '{}'</value>
2127+
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2128+
</data>
21212129
<data name="WSLCCLI_RootCommandDesc" xml:space="preserve">
21222130
<value>WSLC is the Windows Subsystem for Linux Container CLI tool.</value>
21232131
<comment>{Locked="WSLC"}Product names should not be translated</comment>
@@ -2277,6 +2285,17 @@ For privacy information about this product please visit https://aka.ms/privacy.<
22772285
<data name="WSLCCLI_SessionTerminateLongDesc" xml:space="preserve">
22782286
<value>Terminates an active session. If no session is specified, the default session will be terminated.</value>
22792287
</data>
2288+
<data name="WSLCCLI_SessionEnterDesc" xml:space="preserve">
2289+
<value>Enter a temporary session.</value>
2290+
</data>
2291+
<data name="WSLCCLI_SessionEnterLongDesc" xml:space="preserve">
2292+
<value>Creates a non-persistent session with the given storage path and opens a shell into it. The session is deleted when the shell exits. If no name is provided, a GUID is generated and printed to stderr.</value>
2293+
<comment>{Locked="GUID"}{Locked="stderr"}Technical terms should not be translated</comment>
2294+
</data>
2295+
<data name="WSLCCLI_SessionEnterNameArgDescription" xml:space="preserve">
2296+
<value>Name for the session. If not provided, a GUID is generated.</value>
2297+
<comment>{Locked="GUID"}Technical terms should not be translated</comment>
2298+
</data>
22802299
<data name="WSLCCLI_SettingsCommandDesc" xml:space="preserve">
22812300
<value>Open the settings file in the default editor.</value>
22822301
</data>
@@ -2371,6 +2390,9 @@ On first run, creates the file with all settings commented out at their defaults
23712390
<data name="WSLCCLI_SessionIdPositionalArgDescription" xml:space="preserve">
23722391
<value>Session ID</value>
23732392
</data>
2393+
<data name="WSLCCLI_SessionStoragePositionalArgDescription" xml:space="preserve">
2394+
<value>Session storage path</value>
2395+
</data>
23742396
<data name="WSLCCLI_SignalArgDescription" xml:space="preserve">
23752397
<value>Signal to send (default: {})</value>
23762398
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>

src/windows/common/WslClient.cpp

Lines changed: 0 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ Module Name:
1919
#include "Distribution.h"
2020
#include "CommandLine.h"
2121
#include <conio.h>
22-
#include "wslc.h"
23-
#include "WSLCProcessLauncher.h"
2422
#include "WslCoreFilesystem.h"
2523

2624
#define BASH_PATH L"/bin/bash"
@@ -30,7 +28,6 @@ using winrt::Windows::Management::Deployment::DeploymentOptions;
3028
using wsl::shared::Localization;
3129
using wsl::windows::common::ClientExecutionContext;
3230
using wsl::windows::common::Context;
33-
using wsl::windows::common::WSLCProcessLauncher;
3431
using namespace wsl::windows::common;
3532
using namespace wsl::shared;
3633
using namespace wsl::windows::common::distribution;
@@ -1521,191 +1518,6 @@ int RunDebugShell()
15211518
THROW_HR(HCS_E_CONNECTION_CLOSED);
15221519
}
15231520

1524-
DEFINE_ENUM_FLAG_OPERATORS(WSLCFeatureFlags);
1525-
1526-
// Temporary debugging tool for WSLC
1527-
int WslcShell(_In_ std::wstring_view commandLine)
1528-
{
1529-
WSLCSessionSettings sessionSettings{};
1530-
sessionSettings.DisplayName = L"WSLCShell";
1531-
sessionSettings.CpuCount = 4;
1532-
sessionSettings.MemoryMb = 4096;
1533-
sessionSettings.NetworkingMode = WSLCNetworkingModeVirtioProxy;
1534-
sessionSettings.BootTimeoutMs = 30 * 1000;
1535-
sessionSettings.MaximumStorageSizeMb = 4096;
1536-
1537-
std::string shell = "/bin/bash";
1538-
std::string cmd;
1539-
1540-
bool help = false;
1541-
bool noTty = false;
1542-
std::wstring debugShell;
1543-
1544-
std::wstring storagePath;
1545-
std::wstring rootVhdOverride;
1546-
std::string rootVhdTypeOverride;
1547-
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
1548-
parser.AddArgument(rootVhdOverride, L"--vhd");
1549-
parser.AddArgument(Utf8String(shell), L"--shell");
1550-
parser.AddArgument(SetFlag<WslcFeatureFlagsDnsTunneling>(sessionSettings.FeatureFlags), L"--dns-tunneling");
1551-
parser.AddArgument(SetFlag<WslcFeatureFlagsVirtioFs>(sessionSettings.FeatureFlags), L"--virtiofs");
1552-
parser.AddArgument(Integer(sessionSettings.MemoryMb), L"--memory");
1553-
parser.AddArgument(Integer(sessionSettings.CpuCount), L"--cpu");
1554-
parser.AddArgument(Utf8String(rootVhdTypeOverride), L"--fstype");
1555-
parser.AddArgument(storagePath, L"--storage");
1556-
parser.AddArgument(Integer(reinterpret_cast<int&>(sessionSettings.NetworkingMode)), L"--networking-mode");
1557-
parser.AddArgument(debugShell, L"--debug-shell");
1558-
parser.AddArgument(noTty, L"--no-tty");
1559-
parser.AddArgument(help, L"--help");
1560-
parser.Parse();
1561-
1562-
if (help)
1563-
{
1564-
const auto usage = std::format(
1565-
LR"({} --wslc [--vhd </path/to/vhd>] [--shell </path/to/shell>] [--memory <memory-mb>] [--cpu <cpus>] [--dns-tunneling] [--virtiofs] [--networking-mode <mode>] [--fstype <fstype>] [--container-vhd </path/to/vhd>] [--help])",
1566-
WSL_BINARY_NAME);
1567-
1568-
wprintf(L"%ls\n", usage.c_str());
1569-
return 1;
1570-
}
1571-
1572-
switch (sessionSettings.NetworkingMode)
1573-
{
1574-
case WSLCNetworkingMode::WSLCNetworkingModeNone:
1575-
case WSLCNetworkingMode::WSLCNetworkingModeNAT:
1576-
case WSLCNetworkingMode::WSLCNetworkingModeVirtioProxy:
1577-
break;
1578-
default:
1579-
THROW_HR(E_INVALIDARG);
1580-
}
1581-
1582-
wil::com_ptr<IWSLCSessionManager> sessionManager;
1583-
THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager)));
1584-
wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get());
1585-
1586-
wil::com_ptr<IWSLCSession> session;
1587-
1588-
if (!rootVhdOverride.empty())
1589-
{
1590-
if (rootVhdTypeOverride.empty())
1591-
{
1592-
wprintf(L"--fstype required when --vhd is passed\n");
1593-
return 1;
1594-
}
1595-
1596-
sessionSettings.RootVhdOverride = rootVhdOverride.c_str();
1597-
sessionSettings.RootVhdTypeOverride = rootVhdTypeOverride.c_str();
1598-
}
1599-
1600-
if (!storagePath.empty())
1601-
{
1602-
storagePath = std::filesystem::weakly_canonical(storagePath).wstring();
1603-
sessionSettings.StoragePath = storagePath.c_str();
1604-
}
1605-
1606-
if (!debugShell.empty())
1607-
{
1608-
THROW_IF_FAILED(sessionManager->OpenSessionByName(debugShell.c_str(), &session));
1609-
}
1610-
else
1611-
{
1612-
THROW_IF_FAILED(sessionManager->CreateSession(&sessionSettings, WSLCSessionFlagsNone, &session));
1613-
}
1614-
1615-
wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
1616-
1617-
std::optional<wil::com_ptr<IWSLCContainer>> container;
1618-
std::optional<wsl::windows::common::ClientRunningWSLCProcess> process;
1619-
// Get the terminal size.
1620-
HANDLE Stdout = GetStdHandle(STD_OUTPUT_HANDLE);
1621-
HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE);
1622-
1623-
CONSOLE_SCREEN_BUFFER_INFOEX Info{};
1624-
Info.cbSize = sizeof(Info);
1625-
THROW_IF_WIN32_BOOL_FALSE(::GetConsoleScreenBufferInfoEx(Stdout, &Info));
1626-
1627-
wsl::windows::common::WSLCProcessLauncher launcher{shell, {shell, "--login"}, {"TERM=xterm-256color"}, WSLCProcessFlagsTty};
1628-
launcher.SetTtySize(Info.srWindow.Bottom - Info.srWindow.Top + 1, Info.srWindow.Right - Info.srWindow.Left + 1);
1629-
1630-
process = launcher.Launch(*session);
1631-
1632-
if (noTty)
1633-
{
1634-
using namespace wsl::windows::common::relay;
1635-
wsl::windows::common::relay::MultiHandleWait io;
1636-
1637-
// Create a thread to relay stdin to the pipe.
1638-
1639-
std::thread inputThread;
1640-
wil::unique_event exitEvent{wil::EventOptions::ManualReset};
1641-
1642-
auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
1643-
if (inputThread.joinable())
1644-
{
1645-
exitEvent.SetEvent();
1646-
inputThread.join();
1647-
}
1648-
});
1649-
1650-
// Required because ReadFile() blocks if stdin is a tty.
1651-
if (wsl::windows::common::wslutil::IsInteractiveConsole())
1652-
{
1653-
inputThread = std::thread{[&]() {
1654-
wsl::windows::common::relay::StandardInputRelay(Stdin, process->GetStdHandle(0).get(), []() {}, exitEvent.get());
1655-
}};
1656-
}
1657-
else
1658-
{
1659-
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(GetStdHandle(STD_INPUT_HANDLE), process->GetStdHandle(0)));
1660-
}
1661-
1662-
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(process->GetStdHandle(1), GetStdHandle(STD_OUTPUT_HANDLE)));
1663-
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(process->GetStdHandle(2), GetStdHandle(STD_ERROR_HANDLE)));
1664-
1665-
io.Run({});
1666-
}
1667-
else
1668-
{
1669-
// Configure console for interactive usage.
1670-
wsl::windows::common::ConsoleState console;
1671-
auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset);
1672-
1673-
std::vector<wil::unique_handle> handleStorage;
1674-
HANDLE ttyInput = nullptr;
1675-
HANDLE ttyOutput = nullptr;
1676-
auto& it = handleStorage.emplace_back(process->GetStdHandle(WSLCFDTty));
1677-
ttyInput = it.get();
1678-
ttyOutput = it.get();
1679-
1680-
{
1681-
// Create a thread to relay stdin to the pipe.
1682-
std::thread inputThread([&]() {
1683-
auto updateTerminal = [&console, &process]() {
1684-
const auto windowSize = console.GetWindowSize();
1685-
LOG_IF_FAILED(process->Get().ResizeTty(windowSize.Y, windowSize.X));
1686-
};
1687-
1688-
wsl::windows::common::relay::StandardInputRelay(Stdin, ttyInput, updateTerminal, exitEvent.get());
1689-
});
1690-
1691-
auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
1692-
exitEvent.SetEvent();
1693-
inputThread.join();
1694-
});
1695-
1696-
// Relay the contents of the pipe to stdout.
1697-
wsl::windows::common::relay::InterruptableRelay(ttyOutput, Stdout);
1698-
}
1699-
}
1700-
1701-
process->GetExitEvent().wait();
1702-
1703-
auto exitCode = process->GetExitCode();
1704-
wprintf(L"%hs exited with: %i", shell.c_str(), exitCode);
1705-
1706-
return exitCode;
1707-
}
1708-
17091521
int WslMain(_In_ std::wstring_view commandLine)
17101522
{
17111523
// Call the MSI package if we're in an MSIX context
@@ -1948,10 +1760,6 @@ int WslMain(_In_ std::wstring_view commandLine)
19481760

19491761
return Uninstall();
19501762
}
1951-
else if (argument == L"--wslc")
1952-
{
1953-
return WslcShell(commandLine);
1954-
}
19551763
else
19561764
{
19571765
if ((argument.size() > 0) && (argument[0] == L'-'))

src/windows/service/exe/WSLCSessionManager.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
5454
// Ensure that the session display name is non-null and not too long.
5555
THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr);
5656
THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
57+
THROW_HR_IF_MSG(
58+
E_INVALIDARG,
59+
WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
60+
"Invalid storage flags: %i",
61+
Settings->StorageFlags);
5762

5863
auto tokenInfo = GetCallingProcessTokenInfo();
5964

@@ -212,6 +217,7 @@ WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(_In_ ULONG
212217
sessionSettings.NetworkingMode = Settings->NetworkingMode;
213218
sessionSettings.FeatureFlags = Settings->FeatureFlags;
214219
sessionSettings.RootVhdTypeOverride = Settings->RootVhdTypeOverride;
220+
sessionSettings.StorageFlags = Settings->StorageFlags;
215221
return sessionSettings;
216222
}
217223

src/windows/service/inc/wslc.idl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,15 @@ interface IWSLCVirtualMachine : IUnknown
415415
HRESULT RemoveShare([in] REFGUID ShareId);
416416
}
417417

418+
typedef enum _WSLCSessionStorageFlags
419+
{
420+
WSLCSessionStorageFlagsNone = 0,
421+
WSLCSessionStorageFlagsNoCreate = 1, // Open an existing storage path, but don't create a new one.
422+
WSLCSessionStorageFlagsValid = WSLCSessionStorageFlagsNoCreate
423+
} WSLCSessionStorageFlags;
424+
425+
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCSessionStorageFlags);")
426+
418427
// Settings for IWSLCSessionManager::CreateSession - full session configuration
419428
typedef struct _WSLCSessionSettings {
420429
LPCWSTR DisplayName;
@@ -427,6 +436,7 @@ typedef struct _WSLCSessionSettings {
427436
[unique] ITerminationCallback* TerminationCallback;
428437
WSLCFeatureFlags FeatureFlags;
429438
WSLCHandle DmesgOutput;
439+
WSLCSessionStorageFlags StorageFlags;
430440

431441
// Below options are used for debugging purposes only.
432442
[unique] LPCWSTR RootVhdOverride;
@@ -554,6 +564,7 @@ typedef struct _WSLCSessionInitSettings
554564
ULONG CreatorPid;
555565
LPCWSTR DisplayName;
556566
LPCWSTR StoragePath;
567+
WSLCSessionStorageFlags StorageFlags;
557568
ULONGLONG MaximumStorageSizeMb;
558569
ULONG BootTimeoutMs;
559570
WSLCNetworkingMode NetworkingMode;
@@ -681,6 +692,9 @@ typedef enum _WSLCSessionFlags
681692
WSLCSessionFlagsOpenExisting = 2, // Open an existing session if the name is in use.
682693
} WSLCSessionFlags;
683694

695+
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCSessionFlags);")
696+
697+
684698
[
685699
uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760),
686700
pointer_default(unique),

src/windows/wslc/arguments/ArgumentDefinitions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ _(Remove, "rm", NO_ALIAS, Kind::Flag, L
7272
/*_(Scheme, "scheme", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SchemeArgDescription())*/ \
7373
_(Session, "session", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SessionIdArgDescription()) \
7474
_(SessionId, "session-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SessionIdPositionalArgDescription()) \
75+
_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \
7576
_(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription(L"SIGKILL")) \
7677
_(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \
7778
_(Time, "time", L"t", Kind::Value, Localization::WSLCCLI_TimeArgDescription()) \

src/windows/wslc/commands/SessionCommand.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace wsl::windows::wslc {
2323
std::vector<std::unique_ptr<Command>> SessionCommand::GetCommands() const
2424
{
2525
std::vector<std::unique_ptr<Command>> commands;
26+
commands.push_back(std::make_unique<SessionEnterCommand>(FullName()));
2627
commands.push_back(std::make_unique<SessionListCommand>(FullName()));
2728
commands.push_back(std::make_unique<SessionShellCommand>(FullName()));
2829
commands.push_back(std::make_unique<SessionTerminateCommand>(FullName()));

src/windows/wslc/commands/SessionCommand.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ struct SessionShellCommand final : public Command
6262
void ExecuteInternal(CLIExecutionContext& context) const override;
6363
};
6464

65+
// Enter Command
66+
struct SessionEnterCommand final : public Command
67+
{
68+
constexpr static std::wstring_view CommandName = L"enter";
69+
SessionEnterCommand(const std::wstring& parent) : Command(CommandName, parent)
70+
{
71+
}
72+
std::vector<Argument> GetArguments() const override;
73+
std::wstring ShortDescription() const override;
74+
std::wstring LongDescription() const override;
75+
76+
protected:
77+
void ExecuteInternal(CLIExecutionContext& context) const override;
78+
};
79+
6580
// Terminate Command
6681
struct SessionTerminateCommand final : public Command
6782
{

0 commit comments

Comments
 (0)