WSL/src/windows/common/WslClient.cpp

1964 lines
69 KiB
C++

/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
WslClient.cpp
Abstract:
This file contains the logic for WSL client entry points.
--*/
#include "precomp.h"
#include "WslInstall.h"
#include "HandleConsoleProgressBar.h"
#include "Distribution.h"
#include "CommandLine.h"
#include <conio.h>
#define BASH_PATH L"/bin/bash"
using winrt::Windows::Foundation::Uri;
using winrt::Windows::Management::Deployment::DeploymentOptions;
using wsl::shared::Localization;
using wsl::windows::common::ClientExecutionContext;
using wsl::windows::common::Context;
using namespace wsl::windows::common;
using namespace wsl::shared;
using namespace wsl::windows::common::distribution;
static bool g_promptBeforeExit = false;
namespace {
enum Entrypoint
{
Bash,
Wsl,
Wslconfig,
Wslg
};
struct LaunchProcessOptions
{
std::wstring CurrentWorkingDirectory;
std::optional<GUID> DistroGuid;
std::wstring Username;
ULONG LaunchFlags = LXSS_LAUNCH_FLAG_ENABLE_INTEROP | LXSS_LAUNCH_FLAG_TRANSLATE_ENVIRONMENT;
};
struct ListOptions
{
bool verbose;
bool quiet;
bool running;
bool all;
bool online;
};
struct ShellExecOptions
{
std::optional<bool> UseShell;
std::optional<bool> Login;
bool DefaultUseShell = true;
bool DefaultLogin = false;
bool IsLogin() const
{
return Login.value_or(DefaultLogin);
}
bool IsUseShell() const
{
return UseShell.value_or(DefaultUseShell);
}
void SetExecMode()
{
UseShell = false;
Login = false;
}
void ParseShellOptionArg(std::wstring_view Argument)
{
if (Argument == WSL_SHELL_OPTION_ARG_LOGIN_OPTION)
{
UseShell = true;
Login = true;
}
else if (Argument == WSL_SHELL_OPTION_ARG_NOSHELL_OPTION)
{
SetExecMode();
}
else if (Argument == WSL_SHELL_OPTION_ARG_STANDARD_OPTION)
{
UseShell = true;
Login = false;
}
else
{
THROW_HR(E_INVALIDARG);
}
}
};
bool IsInteractiveConsole()
{
const HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode{};
return GetFileType(stdinHandle) == FILE_TYPE_CHAR && GetConsoleMode(stdinHandle, &mode);
}
void PromptForKeyPress()
{
if (IsInteractiveConsole())
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePressAnyKeyToExit());
LOG_IF_WIN32_BOOL_FALSE(FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)));
_getch();
}
}
// Forward function declarations.
bool InstallPrerequisites(_In_ bool installWslOptionalComponent);
int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options);
int ListDistributionsHelper(_In_ ListOptions options);
LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine);
DWORD ParseVersionString(_In_ const std::wstring_view& versionString);
int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe);
int Version();
template <typename T>
struct WslVersion
{
T& value;
int operator()(LPCWSTR Input) const
{
if (Input == nullptr)
{
return -1;
}
value = ParseVersionString(Input);
return 1;
}
};
// Function definitions.
int BashMain(_In_ std::wstring_view commandLine)
{
// Call the MSI package if we're in an MSIX context
if (wsl::windows::common::wslutil::IsRunningInMsix())
{
return wsl::windows::common::wslutil::CallMsiPackage();
}
const auto options = ParseLegacyArguments(commandLine);
// If the command line is empty, construct the arguments in the following
// format to launch bash as a login shell:
//
// filename = /bin/bash
// argv[0] = -bash
//
// N.B. This is the same logic that login uses to launch the shell.
//
// For non-empty command lines, construct the arguments in the following
// format:
//
// filename = /bin/bash
// argv[0] = /bin/bash
// argv[1] = -c
// argv[2] = /bin/bash -c "commandLine"
//
// N.B. The arguments are set up this way to leave /bin/bash in charge of
// all argument parsing.
int argc = 1;
LPCWSTR argv[3];
std::wstring arguments;
LPCWSTR filename;
if (commandLine.empty())
{
argv[0] = L"-bash";
filename = BASH_PATH;
}
else
{
argc = RTL_NUMBER_OF(argv);
arguments = BASH_PATH L" ";
arguments.append(commandLine);
argv[0] = BASH_PATH;
argv[1] = L"-c";
argv[2] = arguments.c_str();
filename = argv[0];
}
return LaunchProcess(filename, argc, argv, options);
}
void ChangeDirectory(_In_ std::wstring_view argument, _Inout_ LaunchProcessOptions& options)
{
std::wstring directory(wsl::windows::common::string::StripQuotes(argument));
THROW_HR_IF(E_INVALIDARG, directory.empty());
// There are two supported directory arguments:
// 1. Any path that begins with a '/' or `~` is assumed to be a Linux path.
// If the path does not exist an error is logged to /dev/kmsg.
// 2. Everything else is assumed to be a valid absolute Windows path.
if ((directory[0] == L'/') || (directory[0] == L'~'))
{
options.CurrentWorkingDirectory = std::move(directory);
}
else
{
THROW_HR_IF(E_INVALIDARG, !std::filesystem::path(directory).is_absolute());
THROW_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(directory.c_str()));
}
}
int ExportDistribution(_In_ std::wstring_view commandLine)
{
ULONG flags = 0;
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
std::filesystem::path filePath;
LPCWSTR name{};
auto parseFormat = [&flags](LPCWSTR Value) {
if (Value == nullptr)
{
return -1;
}
if (wsl::shared::string::IsEqual(L"tar.gz", Value))
{
WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP);
}
else if (wsl::shared::string::IsEqual(L"tar.xz", Value))
{
WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_XZIP);
}
else if (wsl::shared::string::IsEqual(L"vhd", Value))
{
WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_VHD);
}
else if (!wsl::shared::string::IsEqual(L"tar", Value))
{
THROW_HR(E_INVALIDARG);
}
return 1;
};
parser.AddPositionalArgument(name, 0);
parser.AddPositionalArgument(filePath, 1);
parser.AddArgument(SetFlag<ULONG, LXSS_EXPORT_DISTRO_FLAGS_VHD>(flags), WSL_EXPORT_ARG_VHD_OPTION);
parser.AddArgument(parseFormat, WSL_EXPORT_ARG_FORMAT_OPTION);
parser.Parse();
THROW_HR_IF(
WSL_E_INVALID_USAGE,
filePath.empty() || (WI_IsFlagSet(flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP) && WI_IsFlagSet(flags, LXSS_EXPORT_DISTRO_FLAGS_VHD)));
// Determine if the target is stdout, or an on-disk file.
wil::unique_hfile file;
HANDLE fileHandle;
if (filePath.wstring() == WSL_EXPORT_ARG_STDOUT)
{
fileHandle = GetStdHandle(STD_OUTPUT_HANDLE);
}
else
{
file.reset(CreateFileW(
filePath.c_str(), GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
THROW_LAST_ERROR_IF(!file);
fileHandle = file.get();
}
// Delete the target if export was unsuccessful.
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
if (file)
{
LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(filePath.c_str()));
}
});
// Export the distribution.
wsl::windows::common::SvcComm service;
const GUID distroId = service.GetDistributionId(name);
{
using wsl::windows::common::HandleConsoleProgressBar;
HandleConsoleProgressBar exportProgress(fileHandle, Localization::MessageExportProgress(), HandleConsoleProgressBar::Format::FileSize);
THROW_IF_FAILED(service.ExportDistribution(&distroId, fileHandle, flags));
}
if (file)
{
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
}
cleanup.release();
return 0;
}
int ImportDistribution(_In_ std::wstring_view commandLine)
{
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
LPCWSTR name{};
std::optional<std::wstring> installPath{};
std::filesystem::path filePath;
ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE;
DWORD version = LXSS_WSL_VERSION_DEFAULT;
parser.AddPositionalArgument(name, 0);
parser.AddPositionalArgument(AbsolutePath(installPath), 1);
parser.AddPositionalArgument(filePath, 2);
parser.AddArgument(WslVersion(version), WSL_IMPORT_ARG_VERSION);
parser.AddArgument(SetFlag<ULONG, LXSS_IMPORT_DISTRO_FLAGS_VHD>{flags}, WSL_IMPORT_ARG_VHD);
parser.Parse();
if (name == nullptr || !installPath.has_value() || filePath.empty())
{
THROW_HR(E_INVALIDARG);
}
// Ensure that the install path exists.
bool directoryCreated = true;
if (!CreateDirectoryW(installPath->c_str(), nullptr))
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
directoryCreated = false;
}
else
{
THROW_LAST_ERROR_MSG("CreateDirectoryW");
}
}
auto directory_cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [directoryCreated, &installPath]() {
if (directoryCreated)
{
LOG_IF_WIN32_BOOL_FALSE(RemoveDirectory(installPath->c_str()));
}
});
// Determine if the source of the tar file is stdin, or an on-disk file.
wil::unique_hfile file;
HANDLE fileHandle;
if (filePath.wstring() == WSL_IMPORT_ARG_STDIN)
{
fileHandle = GetStdHandle(STD_INPUT_HANDLE);
}
else
{
if (WI_IsFlagClear(flags, LXSS_IMPORT_DISTRO_FLAGS_VHD))
{
// Fail if expecting a tar, but the file name has the .vhd or .vhdx extension.
if (wsl::windows::common::wslutil::IsVhdFile(filePath))
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePassVhdFlag());
return -1;
}
}
file.reset(CreateFileW(
filePath.c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
THROW_LAST_ERROR_IF(!file);
fileHandle = file.get();
}
// Register the distribution.
{
wsl::windows::common::HandleConsoleProgressBar progressBar(fileHandle, Localization::MessageImportProgress());
wsl::windows::common::SvcComm service;
service.RegisterDistribution(name, version, fileHandle, installPath->c_str(), flags);
}
directory_cleanup.release();
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int ImportDistributionInplace(_In_ std::wstring_view commandLine)
{
// Parse the command line.
int argc = 0;
const wil::unique_hlocal_ptr<LPWSTR[]> argv{CommandLineToArgvW(std::wstring(commandLine).c_str(), &argc)};
THROW_LAST_ERROR_IF(!argv);
THROW_HR_IF(WSL_E_INVALID_USAGE, argc != 2);
const auto name(argv[0]);
const auto filePath = wsl::windows::common::filesystem::GetFullPath(argv[1]);
wsl::windows::common::SvcComm service;
service.ImportDistributionInplace(name, filePath.c_str());
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int LaunchElevated(_In_ LPCWSTR commandLine)
{
wsl::windows::common::wslutil::PrintMessage(
wsl::windows::common::wslutil::GetSystemErrorString(HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED)));
// Add the attach parent process argument to the command line and shell execute an elevated version of wsl.exe.
std::wstring arguments;
arguments += WSL_PARENT_CONSOLE_ARG L" ";
arguments += std::to_wstring(GetCurrentProcessId());
arguments += L" ";
arguments += commandLine;
const auto path = wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle());
SHELLEXECUTEINFOW execInfo{};
execInfo.cbSize = sizeof(execInfo);
execInfo.fMask = (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI);
execInfo.lpFile = path.c_str();
execInfo.lpVerb = L"runas";
execInfo.nShow = SW_HIDE;
execInfo.lpParameters = arguments.c_str();
THROW_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&execInfo));
const wil::unique_handle process{execInfo.hProcess};
// Get the process exit code.
WI_VERIFY(WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0);
DWORD exitCode;
THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(process.get(), &exitCode));
return static_cast<int>(exitCode);
}
int Install(_In_ std::wstring_view commandLine)
{
// Parse options.
std::optional<std::wstring> distroArgument;
std::optional<std::wstring> fromFile;
std::optional<std::wstring> name;
std::optional<std::filesystem::path> location;
std::optional<ULONG> version;
std::optional<uint64_t> vhdSize;
bool fixedVhd = false;
bool installWslOptionalComponent = false;
bool noLaunchAfterInstall = false;
bool noDistribution = false;
bool legacy = false;
bool webDownload = IsWindowsServer();
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddPositionalArgument(distroArgument, 0);
parser.AddArgument(distroArgument, WSL_INSTALL_ARG_DIST_OPTION_LONG, WSL_INSTALL_ARG_DIST_OPTION);
parser.AddArgument(noLaunchAfterInstall, WSL_INSTALL_ARG_NO_LAUNCH_OPTION_LONG, WSL_INSTALL_ARG_NO_LAUNCH_OPTION);
parser.AddArgument(webDownload, WSL_INSTALL_ARG_WEB_DOWNLOAD_LONG);
parser.AddArgument(noDistribution, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION);
parser.AddArgument(installWslOptionalComponent, WSL_INSTALL_ARG_ENABLE_WSL1_LONG);
parser.AddArgument(NoOp{}, WSL_INSTALL_ARG_PRERELEASE_LONG); // Unused but handled because argument may be present when invoked from inbox.
parser.AddArgument(fromFile, WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_FROM_FILE_OPTION);
parser.AddArgument(name, WSL_INSTALL_ARG_NAME_LONG);
parser.AddArgument(AbsolutePath(location), WSL_INSTALL_ARG_LOCATION_LONG, WSL_INSTALL_ARG_LOCATION_OPTION);
parser.AddArgument(legacy, WSL_INSTALL_ARG_LEGACY_LONG);
parser.AddArgument(WslVersion(version), WSL_INSTALL_ARG_VERSION);
parser.AddArgument(g_promptBeforeExit, WSL_INSTALL_ARG_PROMPT_BEFORE_EXIT_OPTION);
parser.AddArgument(SizeString(vhdSize), WSL_INSTALL_ARG_VHD_SIZE);
parser.AddArgument(fixedVhd, WSL_INSTALL_ARG_FIXED_VHD);
parser.Parse();
if (noDistribution && distroArgument.has_value())
{
THROW_HR_WITH_USER_ERROR(
E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, WSL_INSTALL_ARG_DIST_OPTION_LONG));
}
if (fixedVhd && !vhdSize.has_value())
{
THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageArgumentNotValidWithout(WSL_INSTALL_ARG_FIXED_VHD, WSL_INSTALL_ARG_VHD_SIZE));
}
// A distribution to be installed can be specified in three ways:
// wsl.exe --install --distribution Ubuntu
// wsl.exe --install Ubuntu
// wsl.exe --install
//
// N.B. The legacy method (specifying --distribution) is no longer documented,
// but is still supported to avoid breaking existing scripts.
if (fromFile.has_value())
{
if (distroArgument.has_value())
{
THROW_HR_WITH_USER_ERROR(
E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_DIST_OPTION_LONG));
}
wil::unique_hfile diskFile;
HANDLE file{};
if (fromFile.value() == WSL_IMPORT_ARG_STDIN)
{
file = GetStdHandle(STD_INPUT_HANDLE);
fromFile = L"<stdin>";
}
else
{
diskFile.reset(CreateFileW(
fromFile->c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
THROW_LAST_ERROR_IF(!diskFile);
file = diskFile.get();
}
wsl::windows::common::wslutil::PrintMessage(Localization::MessageInstalling(fromFile->c_str()));
wsl::windows::common::HandleConsoleProgressBar progressBar(file, Localization::MessageImportProgress());
SvcComm service;
auto [id, installedName] = service.RegisterDistribution(
name.has_value() ? name->c_str() : nullptr,
version.value_or(LXSS_WSL_VERSION_DEFAULT),
file,
location.has_value() ? location->c_str() : nullptr,
fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0,
vhdSize);
wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installedName.get()), stdout);
if (!noLaunchAfterInstall)
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installedName.get()), stdout);
LaunchProcessOptions options{};
options.DistroGuid = id;
return LaunchProcess(nullptr, 0, nullptr, options);
}
return 0;
}
bool rebootRequired = InstallPrerequisites(installWslOptionalComponent);
noLaunchAfterInstall |= rebootRequired;
// Install a distribution only if no reboot is required, or if we're on the --legacy path (to maintain old behavior).
const Distribution* legacyDistro = nullptr;
WslInstall::InstallResult installResult{};
if (!noDistribution && (legacy || !rebootRequired))
{
auto result = WslInstall::InstallDistribution(
installResult, distroArgument, version, !noLaunchAfterInstall, webDownload, legacy, fixedVhd, name, location, vhdSize);
std::optional<std::wstring> flavor;
if (installResult.Distribution.has_value())
{
if (const auto* distro = std::get_if<ModernDistributionVersion>(&*installResult.Distribution))
{
flavor = distro->Name;
}
else
{
legacyDistro = std::get_if<Distribution>(&*installResult.Distribution);
WI_ASSERT(legacyDistro != nullptr);
flavor = legacyDistro->Name;
}
}
// Logs when a specific distribution is installed, and whether that was successful. Used to report distro usage to distro maintainers
WSL_LOG_TELEMETRY(
"InstallDistribution",
PDT_ProductAndServiceUsage,
TraceLoggingValue(result, "result"),
TraceLoggingValue(legacyDistro == nullptr, "modern"),
TraceLoggingValue(flavor.value_or(L"<none>").c_str(), "flavor"));
THROW_IF_FAILED(result);
}
if (rebootRequired)
{
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED);
}
else if (noDistribution)
{
wsl::windows::common::wslutil::PrintSystemError(NO_ERROR);
}
else
{
if (!installResult.Alreadyinstalled)
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installResult.Name));
}
if (!noLaunchAfterInstall)
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installResult.Name), stdout);
if (legacyDistro != nullptr)
{
wsl::windows::common::distribution::Launch(*legacyDistro, installResult.InstalledViaGitHub, !installResult.Alreadyinstalled);
}
else
{
LaunchProcessOptions options{};
options.DistroGuid = installResult.Id.value();
return LaunchProcess(nullptr, 0, nullptr, options);
}
}
}
return 0;
}
bool InstallPrerequisites(_In_ bool installWslOptionalComponent)
{
const auto [rebootRequired, missingComponents] = WslInstall::CheckForMissingOptionalComponents(installWslOptionalComponent);
if (missingComponents.empty())
{
return rebootRequired;
}
// Install any optional components that have not yet been installed.
const auto token = wil::open_current_access_token();
if (!wsl::windows::common::security::IsTokenElevated(token.get()))
{
const auto elevatedCommand = std::format(
L"{} {} {}", WSL_INSTALL_ARG, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, installWslOptionalComponent ? WSL_INSTALL_ARG_ENABLE_WSL1_LONG : L"");
const auto exitCode = LaunchElevated(elevatedCommand.c_str());
if (exitCode != 0)
{
return exitCode;
}
}
else
{
WslInstall::InstallOptionalComponents(missingComponents);
}
return rebootRequired;
}
int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options)
{
// Create an instance of the specified distribution.
//
// N.B. If creating the instance fails because the file system needs to
// be upgraded, the appropriate message is displayed before
// re-attempting the create while allowing the upgrade. This is
// only done if running in interactive mode.
const LPCGUID distribution = options.DistroGuid.has_value() ? &options.DistroGuid.value() : nullptr;
wsl::windows::common::SvcComm service;
if (argc == 0)
{
ClientExecutionContext context;
const auto result = service.CreateInstanceNoThrow(distribution, 0, context.OutError());
if (FAILED(result))
{
if (result == WSL_E_FS_UPGRADE_NEEDED)
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageFsUpgradeNeeded(), stderr);
}
else
{
THROW_HR(result);
}
}
}
const int exitCode = service.LaunchProcess(
distribution,
filename,
argc,
argv,
options.LaunchFlags,
options.Username.empty() ? nullptr : options.Username.c_str(),
options.CurrentWorkingDirectory.empty() ? nullptr : options.CurrentWorkingDirectory.c_str());
THROW_HR_IF(WSL_E_USER_NOT_FOUND, (exitCode == LX_INIT_USER_NOT_FOUND));
THROW_HR_IF(WSL_E_TTY_LIMIT, (exitCode == LX_INIT_TTY_LIMIT));
return exitCode;
}
int ListDistributions(_In_ std::wstring_view commandLine)
{
ListOptions options{};
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddArgument(options.all, WSL_LIST_ARG_ALL_OPTION);
parser.AddArgument(options.running, WSL_LIST_ARG_RUNNING_OPTION);
parser.AddArgument(options.quiet, WSL_LIST_ARG_QUIET_OPTION_LONG, WSL_LIST_ARG_QUIET_OPTION);
parser.AddArgument(options.verbose, WSL_LIST_ARG_VERBOSE_OPTION_LONG, WSL_LIST_ARG_VERBOSE_OPTION);
parser.AddArgument(options.online, WSL_LIST_ARG_ONLINE_OPTION_LONG, WSL_LIST_ARG_ONLINE_OPTION);
parser.Parse();
return ListDistributionsHelper(options);
}
int ListDistributionsHelper(_In_ ListOptions options)
{
// Handle invalid options.
THROW_HR_IF(
WSL_E_INVALID_USAGE,
((options.quiet && options.verbose) || (options.all && options.running)) || ((options.verbose || options.all) && options.online));
// Query all registered distributions and sort the list so the default
// (if present) is first.
wsl::windows::common::SvcComm service;
auto distros = service.EnumerateDistributions();
std::sort(distros.begin(), distros.end(), [](const auto& Left, const auto&) {
return (WI_IsFlagSet(Left.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT));
});
if (options.verbose)
{
THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty());
// Determine max length of a distro name and construct the format string.
size_t maxLength = wcslen(WSL_LIST_HEADER_NAME);
std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
const size_t length = wcslen(entry.DistroName);
if (length > maxLength)
{
maxLength = length;
}
});
std::wstring formatString(L"%s %-");
formatString += std::to_wstring(maxLength + 4);
formatString += L"s%-16s%s\n";
// Print distribution information.
wprintf(formatString.c_str(), L" ", WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_STATE, WSL_LIST_HEADER_VERSION);
std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
const LPCWSTR defaultDistro = WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT) ? L"*" : L" ";
const std::wstring version(std::to_wstring(entry.Version));
auto state = L"Stopped";
switch (entry.State)
{
case LxssDistributionStateRunning:
state = L"Running";
break;
case LxssDistributionStateInstalling:
state = L"Installing";
break;
case LxssDistributionStateUninstalling:
state = L"Uninstalling";
break;
case LxssDistributionStateConverting:
state = L"Converting";
break;
case LxssDistributionStateExporting:
state = L"Exporting";
break;
default:
break;
}
wprintf(formatString.c_str(), defaultDistro, entry.DistroName, state, version.c_str());
});
}
else if (!options.online)
{
if (options.running)
{
std::erase_if(distros, [&](const auto& entry) { return (entry.State != LxssDistributionStateRunning); });
if ((!options.quiet) && (distros.empty()))
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageNoRunningDistro());
return -1;
}
}
if (!options.all)
{
std::erase_if(distros, [&](const auto& entry) {
return (
(entry.State == LxssDistributionStateInstalling) || (entry.State == LxssDistributionStateUninstalling) ||
(entry.State == LxssDistributionStateConverting) || (entry.State == LxssDistributionStateExporting));
});
}
if (!options.quiet)
{
THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty());
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageRegisteredDistrosHeader());
}
std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
if ((!options.quiet) && WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT))
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessagePrintDistroDefault(entry.DistroName), stdout);
}
else
{
wprintf(L"%s\n", entry.DistroName);
}
});
}
else
{
std::vector<std::pair<std::wstring, std::wstring>> names;
size_t maxLength = wcslen(WSL_LIST_HEADER_NAME);
auto appendIfNotPresent = [&](const std::wstring& name, const std::wstring& friendlyName) {
auto pred = [&name](const auto& e) { return e.first == name; };
if (std::find_if(names.begin(), names.end(), pred) == names.end())
{
names.emplace_back(name, friendlyName);
if (name.size() > maxLength)
{
maxLength = name.size();
}
}
};
auto readNames = [&](const DistributionList& distributions) {
if (distributions.ModernDistributions.has_value())
{
for (const auto& [name, versions] : *distributions.ModernDistributions)
{
for (auto i = 0; i < versions.size(); i++)
{
if (!options.all && i > 3)
{
break; // Only show 3 entries per distro unless --all is passed.
}
appendIfNotPresent(versions[i].Name, versions[i].FriendlyName);
}
}
}
if (distributions.Distributions.has_value())
{
for (const auto& e : *distributions.Distributions)
{
appendIfNotPresent(e.Name, e.FriendlyName);
}
}
};
const auto manifest = wsl::windows::common::distribution::GetAvailable();
if (manifest.OverrideManifest.has_value())
{
readNames(*manifest.OverrideManifest);
}
readNames(manifest.Manifest);
std::wstring formatString(L"%-");
formatString += std::to_wstring(maxLength + 4);
formatString += L"s%s\n";
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistributionListOnline(WSL_INSTALL_ARG));
wprintf(formatString.c_str(), WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_FRIENDLY_NAME);
std::for_each(names.begin(), names.end(), [&](const auto& entry) {
wprintf(formatString.c_str(), entry.first.c_str(), entry.second.c_str());
});
}
return 0;
}
int Manage(_In_ std::wstring_view commandLine)
{
LPCWSTR distribution{};
std::optional<bool> sparse;
std::optional<std::wstring> move;
std::optional<std::wstring> defaultUser;
std::optional<uint64_t> resize;
bool allowUnsafe = false;
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME, 0);
parser.AddPositionalArgument(distribution, 0);
parser.AddArgument(ParsedBool(sparse), WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, WSL_MANAGE_ARG_SET_SPARSE_OPTION);
parser.AddArgument(AbsolutePath(move), WSL_MANAGE_ARG_MOVE_OPTION_LONG, WSL_MANAGE_ARG_MOVE_OPTION);
parser.AddArgument(defaultUser, WSL_MANAGE_ARG_SET_DEFAULT_USER_OPTION_LONG);
parser.AddArgument(SizeString(resize), WSL_MANAGE_ARG_RESIZE_OPTION_LONG, WSL_MANAGE_ARG_RESIZE_OPTION);
parser.AddArgument(allowUnsafe, WSL_MANAGE_ARG_ALLOW_UNSAFE);
parser.Parse();
THROW_HR_IF(WSL_E_INVALID_USAGE, distribution == nullptr);
wsl::windows::common::SvcComm service;
auto distroGuid = service.GetDistributionId(distribution);
if (sparse.has_value() + move.has_value() + defaultUser.has_value() + resize.has_value() != 1)
{
THROW_HR(WSL_E_INVALID_USAGE);
}
if (sparse)
{
SetSparse(distroGuid, sparse.value(), allowUnsafe);
}
else if (move)
{
service.MoveDistribution(distroGuid, move->c_str());
}
else if (defaultUser)
{
auto wslExe = wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle());
auto commandLine = std::format(
L"\"{}\" {} -u root /usr/bin/id -u -- '{}'",
wslExe,
wsl::shared::string::GuidToString<wchar_t>(distroGuid),
defaultUser.value());
wsl::windows::common::SubProcess process{wslExe.c_str(), commandLine.c_str()};
auto result = process.RunAndCaptureOutput(INFINITE, GetStdHandle(STD_ERROR_HANDLE));
if (result.ExitCode != 0)
{
return result.ExitCode;
}
while (!result.Stdout.empty() && (result.Stdout.back() == '\r' || result.Stdout.back() == '\n'))
{
result.Stdout.pop_back();
}
wchar_t* endPtr{};
auto newUid = std::wcstoul(result.Stdout.c_str(), &endPtr, 10);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), endPtr != result.Stdout.c_str() + result.Stdout.size());
service.ConfigureDistribution(&distroGuid, newUid, LXSS_DISTRO_FLAGS_UNCHANGED);
}
else if (resize)
{
THROW_IF_FAILED(service.ResizeDistribution(&distroGuid, resize.value()));
}
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int Mount(_In_ std::wstring_view commandLine)
{
bool vhd = false;
bool bare = false;
std::optional<std::wstring> options;
ULONG partition = 0;
std::optional<std::wstring> type;
std::optional<std::wstring> name;
std::wstring disk;
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddArgument(bare, WSL_MOUNT_ARG_BARE_OPTION_LONG);
parser.AddArgument(vhd, WSL_MOUNT_ARG_VHD_OPTION_LONG);
parser.AddArgument(options, WSL_MOUNT_ARG_OPTIONS_OPTION_LONG, WSL_MOUNT_ARG_OPTIONS_OPTION);
parser.AddArgument(Integer(partition), WSL_MOUNT_ARG_PARTITION_OPTION_LONG, WSL_MOUNT_ARG_PARTITION_OPTION);
parser.AddArgument(type, WSL_MOUNT_ARG_TYPE_OPTION_LONG, WSL_MOUNT_ARG_TYPE_OPTION);
parser.AddArgument(name, WSL_MOUNT_ARG_NAME_OPTION_LONG, WSL_MOUNT_ARG_NAME_OPTION);
parser.AddPositionalArgument(UnquotedPath(disk), 0);
parser.Parse();
THROW_HR_IF(WSL_E_INVALID_USAGE, disk.empty());
ULONG flags = 0;
if (vhd)
{
WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_VHD);
disk = wsl::windows::common::filesystem::GetFullPath(disk.c_str()).wstring();
}
else
{
WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH);
}
// First attach the disk to the vm
wsl::windows::common::SvcComm service;
const auto result = service.AttachDisk(disk.c_str(), flags);
if (FAILED(result))
{
THROW_HR_IF(result, bare);
// In the case of a non-bare mount, WSL_E_DISK_ALREADY_ATTACHED and LXSS_E_USER_VHD_ALREADY_ATTACHED are
// ok to ignore because the user can mount more than one partition on the same disk
// (so that disk might be already attached).
THROW_HR_IF(result, result != WSL_E_DISK_ALREADY_ATTACHED && result != WSL_E_USER_VHD_ALREADY_ATTACHED);
}
// Perform the mount
if (!bare)
{
const auto mountResult = service.MountDisk(
disk.c_str(),
flags,
partition,
name.has_value() ? name->c_str() : nullptr,
type.has_value() ? type->c_str() : nullptr,
options.has_value() ? options->c_str() : nullptr);
if (mountResult.Result != 0)
{
wsl::windows::common::wslutil::PrintMessage(
Localization::MessageDiskMountFailed(strerror(-mountResult.Result), WSL_UNMOUNT_ARG, disk), stdout);
return 1;
}
else
{
wsl::windows::common::wslutil::PrintMessage(
Localization::MessageDiskMounted(mountResult.MountName.get(), WSL_UNMOUNT_ARG, disk), stdout);
}
}
else
{
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
}
return 0;
}
LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine)
{
// Strip the executable name. Because this has to be a legal file name, quoted parts cannot contain escaped quotes.
BOOLEAN inQuotes = FALSE;
while ((!commandLine.empty()) && ((inQuotes != FALSE) || (!LXSS_IS_WHITESPACE(commandLine[0]))))
{
if (commandLine[0] == L'"')
{
inQuotes = !inQuotes;
}
commandLine = commandLine.substr(1);
}
// Strip any leading whitespace.
commandLine = wsl::windows::common::string::StripLeadingWhitespace(commandLine);
// Check for a distribution GUID as the first parameter and strip it out if present.
auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
auto distroGuid = wsl::shared::string::ToGuid(argument);
if (distroGuid.has_value())
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
}
// Check for the home directory parameter and strip it out if present.
std::wstring currentWorkingDirectory;
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument == WSL_CWD_HOME)
{
currentWorkingDirectory = WSL_CWD_HOME;
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
}
return {std::move(currentWorkingDirectory), std::move(distroGuid)};
}
DWORD
ParseVersionString(_In_ const std::wstring_view& versionString)
{
DWORD version;
const auto result = wil::ResultFromException([&]() { version = std::stoi(std::wstring(versionString)); });
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_VERSION_PARSE_ERROR), (FAILED(result) || ((version != LXSS_WSL_VERSION_1) && (version != LXSS_WSL_VERSION_2))));
return version;
}
int SetDefaultDistribution(_In_ LPCWSTR distributionName)
{
wsl::windows::common::SvcComm service;
const GUID distroGuid = service.GetDistributionId(distributionName);
service.SetDefaultDistribution(&distroGuid);
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int SetDefaultVersion(_In_ std::wstring_view commandLine)
{
const auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
const auto version = ParseVersionString(argument);
if (version == LXSS_WSL_VERSION_1)
{
THROW_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !wsl::windows::common::helpers::IsWslOptionalComponentPresent());
}
else
{
WI_ASSERT(version == LXSS_WSL_VERSION_2);
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo());
}
const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
wsl::windows::common::registry::WriteDword(lxssKey.get(), nullptr, LXSS_WSL_DEFAULT_VERSION, version);
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int Shutdown(_In_ std::wstring_view commandLine)
{
bool force = false;
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddArgument(force, WSL_SHUTDOWN_OPTION_FORCE);
parser.Parse();
wsl::windows::common::SvcComm service;
service.Shutdown(force);
return 0;
}
int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe)
{
wsl::windows::common::SvcComm service;
auto setProgress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart());
THROW_IF_FAILED(service.SetSparse(&distroGuid, sparse, allowUnsafe));
return 0;
}
int SetVersion(_In_ std::wstring_view commandLine)
{
auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SET_VERSION_ARG), stdout);
return -1;
}
const std::wstring distributionName(argument);
wsl::windows::common::SvcComm service;
const auto distroGuid = service.GetDistributionId(distributionName.c_str());
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
const auto version = ParseVersionString(argument);
if (version == LXSS_WSL_VERSION_2)
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo());
}
auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart(), true);
const auto result = service.SetVersion(&distroGuid, version);
progress.End();
THROW_IF_FAILED(result);
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int Status()
{
// Print the default distro.
wsl::windows::common::SvcComm service;
const auto distros = service.EnumerateDistributions();
for (const auto& entry : distros)
{
if (WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT))
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultDistro(entry.DistroName), stdout);
break;
}
}
// Print the default version.
const DWORD version = wsl::windows::common::wslutil::GetDefaultVersion();
wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultVersion(version), stdout);
// Print a message if the WSL optional component is not present for WSL1 support.
if (!wsl::windows::common::helpers::IsWslOptionalComponentPresent())
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageWsl1NotSupported());
}
// Print a message if the vmcompute service is present for WSL2 support.
if (!wsl::windows::common::helpers::IsServicePresent(L"vmcompute"))
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageEnableVirtualization());
}
return 0;
}
int TerminateDistribution(_In_ LPCWSTR distributionName)
{
wsl::windows::common::SvcComm service;
const GUID distroGuid = service.GetDistributionId(distributionName);
service.TerminateInstance(&distroGuid);
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int Unmount(_In_ const std::wstring& arg)
{
const auto* disk = arg.empty() ? nullptr : arg.c_str();
std::pair<int, int> value;
wsl::windows::common::SvcComm service;
const HRESULT result = wil::ResultFromException([&] { value = service.DetachDisk(disk); });
// support relative paths in unmount
// check is the result is the error code for "file not found" and the path is relative
if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && PathIsRelative(disk))
{
// retry dismounting with the absolute path
const auto absoluteDisk = wsl::windows::common::filesystem::GetFullPath(filesystem::UnquotePath(disk).c_str());
value = service.DetachDisk(absoluteDisk.c_str());
}
else if (FAILED(result))
{
THROW_HR(result);
}
if (value.first != 0)
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageDetachFailed(strerror(-value.first), WSL_SHUTDOWN_ARG), stdout);
return -1;
}
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int UnregisterDistribution(_In_ LPCWSTR distributionName)
{
auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageStatusUnregistering(), true);
wsl::windows::common::SvcComm service;
const GUID distroGuid = service.GetDistributionId(distributionName, LXSS_GET_DISTRO_ID_LIST_ALL);
service.UnregisterDistribution(&distroGuid);
progress.End();
wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
return 0;
}
int UpdatePackage(std::wstring_view commandLine)
{
ExecutionContext context(wsl::windows::common::UpdatePackage);
bool preRelease{};
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddArgument(preRelease, WSL_UPDATE_ARG_PRE_RELEASE_OPTION_LONG);
// Options kept for compatibility with inbox WSL.
parser.AddArgument(NoOp(), WSL_UPDATE_ARG_WEB_DOWNLOAD_OPTION_LONG);
parser.AddArgument(NoOp(), WSL_UPDATE_ARG_CONFIRM_OPTION_LONG);
parser.AddArgument(NoOp(), WSL_UPDATE_ARG_PROMPT_OPTION_LONG);
parser.Parse();
return wsl::windows::common::wslutil::UpdatePackage(preRelease, false);
}
int Uninstall()
{
auto logFile = std::filesystem::temp_directory_path() / L"wsl-uninstall-logs.txt";
auto clearLogs =
wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile.c_str())); });
const auto exitCode = wsl::windows::common::wslutil::UninstallViaMsi(logFile.c_str(), &wsl::windows::common::wslutil::MsiMessageCallback);
if (exitCode != 0)
{
clearLogs.release();
THROW_HR_WITH_USER_ERROR(
HRESULT_FROM_WIN32(exitCode),
wsl::shared::Localization::MessageUninstallFailed(exitCode) + L"\r\n" +
wsl::shared::Localization::MessageSeeLogFile(logFile.c_str()));
}
return exitCode;
}
int Version()
{
// Query the Windows version.
const auto windowsVersion = wsl::windows::common::helpers::GetWindowsVersionString();
wsl::windows::common::wslutil::PrintMessage(
Localization::MessagePackageVersions(
WSL_PACKAGE_VERSION, KERNEL_VERSION, WSLG_VERSION, MSRDC_VERSION, DIRECT3D_VERSION, DXCORE_VERSION, windowsVersion),
stdout);
if constexpr (!wsl::shared::OfficialBuild)
{
// Print additional information if running a debug build.
wsl::windows::common::wslutil::PrintMessage(Localization::MessageBuildInfo(_MSC_VER, COMMIT_HASH, __TIME__ " " __DATE__), stdout);
}
return 0;
}
int WslconfigMain(_In_ int argc, _In_reads_(argc) LPWSTR* argv)
{
// Call the MSI package if we're in an MSIX context
if (wsl::windows::common::wslutil::IsRunningInMsix())
{
return wsl::windows::common::wslutil::CallMsiPackage();
}
using wsl::shared::string::IsEqual;
// Use exit code -1 on generic failures. This was the original exit code and shouldn't be changed, especially since wslconfig.exe is deprecated.
int exitCode = -1;
if ((argc >= 2) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_LIST, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_LIST_SHORT, true))))
{
ListOptions options{};
for (int index = 2; index < argc; index += 1)
{
std::wstring_view argument = argv[index];
if (argument.empty())
{
break;
}
if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_ALL, true))
{
options.all = true;
}
else if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_RUNNING, true))
{
options.running = true;
}
else
{
THROW_HR(WSL_E_INVALID_USAGE);
}
}
exitCode = ListDistributionsHelper(options);
}
else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT_SHORT, true))))
{
exitCode = SetDefaultDistribution(argv[2]);
}
else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE_SHORT, true))))
{
exitCode = TerminateDistribution(argv[2]);
}
else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION_SHORT, true))))
{
exitCode = UnregisterDistribution(argv[2]);
}
else
{
THROW_HR(WSL_E_INVALID_USAGE);
}
return exitCode;
}
int WslgMain(_In_ std::wstring_view commandLine)
{
// N.B. There is no app execution alias for wslg, so it cannot run in an MSIX context.
WI_ASSERT(!wsl::windows::common::wslutil::IsRunningInMsix());
auto options = ParseLegacyArguments(commandLine);
// Parse additional arguments.
std::wstring_view argument;
ShellExecOptions shellExecOptions{};
wsl::windows::common::SvcComm service;
for (;;)
{
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
break;
}
if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG))
{
THROW_HR_IF(WSL_E_INVALID_USAGE, options.DistroGuid.has_value());
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty());
// Query the service for the distribution id.
options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str());
}
else if (argument == WSL_SHELL_OPTION_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
THROW_HR_IF(E_INVALIDARG, argument.empty());
shellExecOptions.ParseShellOptionArg(argument);
}
else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG))
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty());
options.Username = argument;
}
else if (argument == WSL_CHANGE_DIRECTORY_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine, true);
ChangeDirectory(argument, options);
}
else if (argument == WSL_STOP_PARSING_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
break;
}
else
{
THROW_HR_IF(WSL_E_INVALID_USAGE, ((argument.size() > 0) && (argument[0] == L'-')));
break;
}
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
}
// Launching a graphical application requires a non-empty command line.
THROW_HR_IF(WSL_E_INVALID_USAGE, commandLine.empty());
std::vector<const wchar_t*> arguments;
const std::wstring commandLineString{commandLine};
wil::unique_hlocal_ptr<LPWSTR[]> execArguments{};
LPCWSTR filename{};
if (!shellExecOptions.IsUseShell())
{
int argc;
execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc));
THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0)));
arguments.reserve(argc);
arguments.insert(arguments.begin(), &execArguments.get()[0], &execArguments.get()[argc]);
filename = arguments[0];
}
else
{
arguments.push_back(commandLineString.c_str());
}
// Graphical applications by default will use a login shell so that users can modify behavior.
shellExecOptions.DefaultUseShell = true;
shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell();
if (shellExecOptions.IsLogin())
{
// Launch via the user's default shell in login mode to parse files like /etc/profile.
WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN);
}
return LaunchProcess(filename, gsl::narrow_cast<int>(arguments.size()), arguments.data(), options);
}
int RunDebugShell()
{
ExecutionContext context(Context::DebugShell);
auto token = wil::open_current_access_token();
auto tokenInfo = wil::get_token_information<TOKEN_USER>(token.get());
auto pipePath = wsl::windows::common::wslutil::GetDebugShellPipeName(tokenInfo->User.Sid);
wil::unique_hfile pipe{CreateFileW(pipePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr)};
if (!pipe)
{
auto error = GetLastError();
if (error == ERROR_ACCESS_DENIED && !wsl::windows::common::security::IsTokenElevated(token.get()))
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageAdministratorAccessRequiredForDebugShell());
return 1;
}
else if (
error == ERROR_FILE_NOT_FOUND &&
!wsl::windows::policies::IsFeatureAllowed(wsl::windows::policies::OpenPoliciesKey().get(), wsl::windows::policies::c_allowDebugShellUserSetting))
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDebugShellDisabled());
return 1;
}
else
{
THROW_WIN32(error);
}
}
// agetty waits for a LF before printing the prompt, so write it immediately after the pipe is opened.
// This is needed because without the '-w' flag, agetty doesn't wait and prints the shell prompt before
// a pipe is connected, so it's lost.
THROW_IF_WIN32_BOOL_FALSE(WriteFile(pipe.get(), "\n", 1, nullptr, nullptr));
// Create a thread to relay stdin to the pipe.
wsl::windows::common::SvcCommIo Io;
auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset);
std::thread inputThread(
[&]() { wsl::windows::common::RelayStandardInput(GetStdHandle(STD_INPUT_HANDLE), pipe.get(), {}, exitEvent.get(), &Io); });
auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
exitEvent.SetEvent();
inputThread.join();
});
// Relay the contents of the pipe to stdout.
wsl::windows::common::relay::InterruptableRelay(pipe.get(), GetStdHandle(STD_OUTPUT_HANDLE));
// Print a message that the VM has exited and signal the input thread to exit.
fputws(L"\n", stdout);
THROW_HR(HCS_E_CONNECTION_CLOSED);
}
int WslMain(_In_ std::wstring_view commandLine)
{
// Call the MSI package if we're in an MSIX context
if (wsl::windows::common::wslutil::IsRunningInMsix())
{
return wsl::windows::common::wslutil::CallMsiPackage();
}
// Use exit code -1 so invokers of wsl.exe can distinguish between a Linux
// process failure and a wsl.exe failure. The distro launcher sample depends
// on this specific code.
int exitCode = -1;
// Parse the command line to determine if the legacy distro GUID or the '~' argument were specified.
auto options = ParseLegacyArguments(commandLine);
// Parse additional arguments.
std::wstring_view argument;
ShellExecOptions shellExecOptions{};
for (;;)
{
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
break;
}
if (argument == WSL_DEBUG_SHELL_ARG_LONG)
{
return RunDebugShell();
}
else if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG))
{
// Ensure the distribution has not already been set.
if (options.DistroGuid.has_value())
{
wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistroAlreadySet());
return exitCode;
}
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRO_ARG_LONG), stdout);
return exitCode;
}
// Query the service for the distribution id.
wsl::windows::common::SvcComm service;
options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str());
}
else if (argument == WSL_CHANGE_DIRECTORY_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine, true);
ChangeDirectory(argument, options);
}
else if (argument == WSL_DISTRIBUTION_ID_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRIBUTION_ID_ARG), stdout);
return exitCode;
}
options.DistroGuid = wsl::shared::string::ToGuid(argument);
THROW_HR_IF(E_INVALIDARG, !options.DistroGuid.has_value());
}
else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG))
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_USER_ARG_LONG), stdout);
return exitCode;
}
options.Username = argument;
}
else if (argument == WSL_UPDATE_ARG)
{
return UpdatePackage(commandLine);
}
else if (argument == WSL_HELP_ARG)
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslUsage());
return exitCode;
}
else if (argument == WSL_STOP_PARSING_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
break;
}
else if ((argument == WSL_EXEC_ARG) || (argument == WSL_EXEC_ARG_LONG))
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
shellExecOptions.SetExecMode();
break;
}
else if (argument == WSL_SHELL_OPTION_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SHELL_OPTION_ARG), stdout);
return exitCode;
}
shellExecOptions.ParseShellOptionArg(argument);
}
else if (argument == WSL_EXPORT_ARG)
{
return ExportDistribution(commandLine);
}
else if (argument == WSL_IMPORT_ARG)
{
return ImportDistribution(commandLine);
}
else if (argument == WSL_IMPORT_INPLACE_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return ImportDistributionInplace(commandLine);
}
else if ((argument == WSL_LIST_ARG) || (argument == WSL_LIST_ARG_LONG))
{
return ListDistributions(commandLine);
}
else if ((argument == WSL_SET_DEFAULT_DISTRO_ARG) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LEGACY) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LONG))
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(
Localization::MessageRequiredParameterMissing(WSL_SET_DEFAULT_DISTRO_ARG_LONG), stdout);
return exitCode;
}
return SetDefaultDistribution(std::wstring(argument).c_str());
}
else if (argument == WSL_PARENT_CONSOLE_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_PARENT_CONSOLE_ARG), stdout);
return exitCode;
}
const auto parentProcessId = std::stoi(std::wstring(argument));
FreeConsole();
THROW_IF_WIN32_BOOL_FALSE(AttachConsole(parentProcessId));
}
else if ((argument == WSL_TERMINATE_ARG) || (argument == WSL_TERMINATE_ARG_LONG))
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_TERMINATE_ARG_LONG), stdout);
return exitCode;
}
return TerminateDistribution(std::wstring(argument).c_str());
}
else if (argument == WSL_UNREGISTER_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
argument = wsl::windows::common::helpers::ParseArgument(commandLine);
if (argument.empty())
{
wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_UNREGISTER_ARG), stdout);
return exitCode;
}
return UnregisterDistribution(std::wstring(argument).c_str());
}
else if (argument == WSL_SET_DEFAULT_VERSION_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return SetDefaultVersion(commandLine);
}
else if (argument == WSL_SHUTDOWN_ARG)
{
return Shutdown(commandLine);
}
else if (argument == WSL_MANAGE_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return Manage(commandLine);
}
else if (argument == WSL_SET_VERSION_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return SetVersion(commandLine);
}
else if (argument == WSL_MOUNT_ARG)
{
return Mount(commandLine);
}
else if (argument == WSL_UNMOUNT_ARG)
{
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return Unmount(std::wstring(commandLine));
}
else if (argument == WSL_INSTALL_ARG)
{
return Install(commandLine);
}
else if (argument == WSL_SYSTEM_DISTRO_ARG)
{
WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_USE_SYSTEM_DISTRO);
}
else if (argument == WSL_STATUS_ARG)
{
return Status();
}
else if ((argument == WSL_VERSION_ARG) || (argument == WSL_VERSION_ARG_LONG))
{
return Version();
}
else if (argument == WSL_UNINSTALL_ARG)
{
return Uninstall();
}
else
{
if ((argument.size() > 0) && (argument[0] == L'-'))
{
std::wstring InvalidArgument(argument);
wsl::windows::common::wslutil::PrintMessage(Localization::MessageInvalidCommandLine(InvalidArgument, WSL_BINARY_NAME), stdout);
return exitCode;
}
break;
}
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
}
// There are three possible cases:
// 1. Empty command line - Launch the default user's default shell.
// 2. Exec mode - Call CommandLineToArgvW on the remaining command
// line and pass it along to the create process call.
// 3. Non-empty command line - The command is invoked through the
// default user's default shell via '$SHELL -c commandLine'.
int argc = 0;
LPCWSTR* arguments{};
LPCWSTR argv[1];
std::wstring commandLineString{commandLine};
wil::unique_hlocal_ptr<LPWSTR[]> execArguments{};
LPCWSTR filename{};
if (!commandLine.empty())
{
if (!shellExecOptions.IsUseShell())
{
execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc));
THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0)));
arguments = const_cast<LPCWSTR*>(execArguments.get());
filename = arguments[0];
}
else
{
argv[0] = commandLineString.c_str();
arguments = argv;
argc = RTL_NUMBER_OF(argv);
}
}
else
{
THROW_HR_IF(E_INVALIDARG, !shellExecOptions.IsUseShell());
}
shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell() && commandLine.empty();
WI_SetFlagIf(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN, shellExecOptions.IsLogin());
// Launch the process.
return LaunchProcess(filename, argc, arguments, options);
}
} // namespace
int wsl::windows::common::WslClient::Main(_In_ LPCWSTR commandLine)
{
wsl::windows::common::EnableContextualizedErrors(false);
// Note WslTraceLoggingUninitialize() is a no-op if WslTraceLoggingInitialize was not called.
auto cleanupTelemetry = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslTraceLoggingUninitialize(); });
std::optional<wsl::windows::common::ExecutionContext> context;
auto entryPoint = Entrypoint::Wsl;
DWORD exitCode;
HRESULT result = S_OK;
try
{
wsl::windows::common::wslutil::ConfigureCrt();
wsl::windows::common::wslutil::InitializeWil();
WslTraceLoggingInitialize(LxssTelemetryProvider, !wsl::shared::OfficialBuild);
// Set CRT encoding.
const char* encoding = getenv("WSL_UTF8");
if (encoding != nullptr && strcmp(encoding, "1") == 0)
{
wsl::windows::common::wslutil::SetCrtEncoding(_O_U8TEXT);
}
else
{
wsl::windows::common::wslutil::SetCrtEncoding(_O_U16TEXT);
}
// Initialize COM.
auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);
wsl::windows::common::wslutil::CoInitializeSecurity();
auto cleanupWinrt = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { winrt::clear_factory_cache(); });
// Initialize winsock.
WSADATA data;
THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data));
// Determine which entrypoint to use.
int argc = 0;
wil::unique_hlocal_ptr<LPWSTR[]> argv{CommandLineToArgvW(commandLine, &argc)};
THROW_HR_IF(E_INVALIDARG, (!argv || (argc == 0)));
auto fileName = std::filesystem::path(argv[0]).stem().wstring();
std::transform(fileName.begin(), fileName.end(), fileName.begin(), tolower);
FILE* warningsFile = nullptr;
const char* disableWarnings = getenv("WSL_DISABLE_WARNINGS");
if (disableWarnings == nullptr || strcmp(disableWarnings, "1") != 0)
{
warningsFile = stderr;
}
if (fileName == L"bash")
{
entryPoint = Entrypoint::Bash;
context.emplace(Context::Bash, warningsFile);
exitCode = BashMain(commandLine);
}
else if (fileName == L"wslconfig")
{
entryPoint = Entrypoint::Wslconfig;
context.emplace(Context::WslConfig, warningsFile);
exitCode = WslconfigMain(argc, argv.get());
}
else if (fileName == L"wslg")
{
entryPoint = Entrypoint::Wslg;
context.emplace(Context::Wslg, warningsFile);
exitCode = WslgMain(commandLine);
}
else
{
context.emplace(Context::Wsl, warningsFile);
exitCode = WslMain(commandLine);
}
}
catch (...)
{
// N.B. bash.exe historically has used 1 instead of -1 to indicate failure.
exitCode = (entryPoint == Entrypoint::Bash) ? 1 : -1;
result = wil::ResultFromCaughtException();
}
// Print error messages for failures.
if (FAILED(result))
{
try
{
std::wstring errorString{};
if (context.has_value() && context->ReportedError().has_value())
{
auto strings = wsl::windows::common::wslutil::ErrorToString(context->ReportedError().value());
// Don't print the error code for WSL_E_DEFAULT_DISTRO_NOT_FOUND and WSL_E_INVALID_USAGE to make the error message easier to read.
if (context->ReportedError()->Code != WSL_E_DEFAULT_DISTRO_NOT_FOUND && context->ReportedError()->Code != WSL_E_INVALID_USAGE)
{
errorString = Localization::MessageErrorCode(strings.Message, strings.Code);
}
else
{
errorString = strings.Message.c_str();
}
// Logs when an error is shown to the user, and what that error is
WSL_LOG_TELEMETRY(
"UserVisibleError",
PDT_ProductAndServicePerformance,
TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
TraceLoggingValue(strings.Code.c_str(), "ErrorCode"));
}
else
{
errorString = wsl::windows::common::wslutil::GetErrorString(result);
}
// For wslg.exe, attempt to print the error message to the parent console, if that fails display a messagebox.
if ((entryPoint == Entrypoint::Wslg) && (!wsl::windows::common::helpers::TryAttachConsole()))
{
auto caption = wsl::shared::Localization::AppName();
LOG_LAST_ERROR_IF(MessageBoxW(nullptr, errorString.c_str(), caption.c_str(), (MB_OK | MB_ICONEXCLAMATION)) == 0);
g_promptBeforeExit = false;
}
else
{
wsl::windows::common::wslutil::PrintMessage(errorString);
//
// If the app was launched via the start menu tile, prompt for input so the
// message does not disappear.
// TODO: This should be replaced with launching the WSL Settings app when that is created.
//
if (entryPoint == Entrypoint::Wsl && winrt::Windows::ApplicationModel::AppInstance::GetActivatedEventArgs() != nullptr)
{
g_promptBeforeExit = true;
}
}
}
CATCH_LOG()
}
if (g_promptBeforeExit)
{
g_promptBeforeExit = false;
PromptForKeyPress();
}
return exitCode;
}