Merge branch 'wsl-for-apps' into user/ptrivedi/saveimage-exportcont

This commit is contained in:
Pooja Trivedi 2026-02-03 12:51:23 -05:00
commit f00e95b790
62 changed files with 6790 additions and 6565 deletions

View File

@ -166,8 +166,8 @@
"FriendlyName": "Arch Linux",
"Default": true,
"Amd64Url": {
"Url": "https://fastly.mirror.pkgbuild.com/wsl/2026.01.01.156076/archlinux-2026.01.01.156076.wsl",
"Sha256": "e3820c60df62edc22df29c9c16d2205512d95c1b086232a9b7bc3960542036d4"
"Url": "https://fastly.mirror.pkgbuild.com/wsl/2026.02.01.158360/archlinux-2026.02.01.158360.wsl",
"Sha256": "e3287ab9f0458240fc6a966849dbc69188bdc7ef376dc27d441aef6f13dd45e5"
}
}
],

View File

@ -292,10 +292,12 @@ a wsl.exe --install &lt;Distro&gt;.</value>
<value>Starší verze distribuce nepodporuje WSL 2.</value>
</data>
<data name="MessageEnableVirtualization" xml:space="preserve">
<value>WsL2 se v aktuální konfiguraci vašeho počítače nepodporuje.
Povolte volitelnou součást „Platforma virtuálního počítače“ a ujistěte se, že je v systému BIOS povolená virtualizace.
Povolte platformu virtuálních počítačů spuštěním příkazu: wsl.exe --install --no-distribution
Informace najdete na https://aka.ms/enablevirtualization</value>
<value>WSL2 nelze spustit, protože na tomto počítači není povolena virtualizace.
Ujistěte se, že je povolena volitelná součást „Virtual Machine Platform“ a že je virtualizace zapnutá v nastavení firmwaru vašeho počítače.
Povolte „Virtual Machine Platform“ spuštěním: wsl.exe --install --no-distribution
Další informace najdete na https://aka.ms/enablevirtualization</value>
<comment>{Locked="--install "}{Locked="--no-distribution
"}Command line arguments, file names and string inserts should not be translated</comment>
</data>

File diff suppressed because it is too large Load Diff

View File

@ -298,10 +298,12 @@ und "wsl.exe --install &lt;Distro&gt;".</value>
<value>WSL 2 wird von der Legacy Distribution nicht unterstütztt.</value>
</data>
<data name="MessageEnableVirtualization" xml:space="preserve">
<value>WSL2 wird von Ihrer aktuellen Computerkonfiguration nicht unterstützt.
Aktivieren Sie die optionale Komponente "Plattform für virtuelle Computer", und stellen Sie sicher, dass die Virtualisierung im BIOS aktiviert ist.
Aktivieren Sie "Plattform für virtuelle Computer", indem Sie ": wsl.exe --install --no-distribution
" ausführen. Weitere Informationen finden Sie unter https://aka.ms/enablevirtualization</value>
<value>WSL2 kann nicht gestartet werden, da die Virtualisierung auf diesem Computer nicht aktiviert ist.
Stellen Sie sicher, dass die optionale Komponente „VM-Plattform“ aktiviert ist und die Virtualisierung in den Firmwareeinstellungen Ihres Computers eingeschaltet ist.
Aktivieren Sie „VM-Plattform“, indem Sie folgenden Befehl ausführen: wsl.exe --install --no-distribution
Weitere Informationen finden Sie unter https://aka.ms/enablevirtualization</value>
<comment>{Locked="--install "}{Locked="--no-distribution
"}Command line arguments, file names and string inserts should not be translated</comment>
</data>

File diff suppressed because it is too large Load Diff

View File

@ -292,10 +292,12 @@ A 'wsl.exe --list --online' parancs használatával listázhatja az elérhető d
<value>Az örökölt disztribúció nem támogatja a WSL 2-t.</value>
</data>
<data name="MessageEnableVirtualization" xml:space="preserve">
<value>A jelenlegi számítógép-konfiguráció nem támogatja a WSL2-t.
Engedélyezze a „Virtuálisgép-platform” választható összetevőt, és győződjön meg arról, hogy a virtualizálás engedélyezve van a BIOS-ban.
<value>A WSL2 nem indítható el, mert a virtualizáció nincs engedélyezve ezen a gépen.
Győződjön meg arról, hogy a „Virtuálisgép-platform” választható összetevő engedélyezve van, és a virtualizáció be van kapcsolva a számítógép firmware-beállításaiban.
A „Virtuálisgép-platform” engedélyezéséhez futtassa a következőt: wsl.exe --install --no-distribution
További információért látogasson el a https://aka.ms/enablevirtualization oldalra</value>
További információért látogasson el ide: https://aka.ms/enablevirtualization</value>
<comment>{Locked="--install "}{Locked="--no-distribution
"}Command line arguments, file names and string inserts should not be translated</comment>
</data>

File diff suppressed because it is too large Load Diff

View File

@ -292,9 +292,11 @@ e "wsl.exe --install &lt;Distro&gt;" para instalar.</value>
<value>A distribuição legada não suporta o WSL 2.</value>
</data>
<data name="MessageEnableVirtualization" xml:space="preserve">
<value>O WSL2 não é suportado com a configuração atual do computador.
Ative o componente opcional "Plataforma de Máquinas Virtuais" e certifique-se de que a virtualização está ativada no BIOS.
Ative a "Plataforma de Máquinas Virtuais" executando: wsl.exe --install --no-distribution
<value>O WSL2 não consegue iniciar porque a virtualização não está ativada nesta máquina.
Certifique-se de que o componente opcional "Plataforma de Máquina Virtual" está ativado e a virtualização está ligada nas definições de firmware do seu computador.
Ative a "Plataforma de Máquina Virtual" ao executar: wsl.exe --install --no-distribution
Para obter informações, visite https://aka.ms/enablevirtualization</value>
<comment>{Locked="--install "}{Locked="--no-distribution
"}Command line arguments, file names and string inserts should not be translated</comment>

View File

@ -169,7 +169,7 @@ Diski ayırmak için '{} {}' wsl.exe çalıştırın.</value>
<value>Sağlanan yükleme konumu zaten kullanılıyor.</value>
</data>
<data name="MessageDistroNameAlreadyExists" xml:space="preserve">
<value>Sağlanan ada sahip bir dağıtım zaten mevcut. Farklı bir isim seçmek için --name seçeneğini kullanın.</value>
<value>Sağlanan ada sahip bir dağıtım zaten mevcut. Farklı bir ad seçmek için --name komutunu kullanın.</value>
<comment>{Locked="--name "}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageDistroNotFound" xml:space="preserve">
@ -1123,7 +1123,7 @@ wsl.exe --manage &lt;DistributionName&gt; --set-sparse true --allow-unsafe</valu
<value>/etc/fstab with mount -a işlenemedi.</value>
</data>
<data name="MessageReadOnlyDistro" xml:space="preserve">
<value>ağıtım diski bağlanırken bir hata oluştu, yedek plan olarak salt okunur şekilde bağlandı.
<value>Dıtım diski bağlanırken bir hata oluştu, yedek plan olarak salt okunur şekilde bağlandı.
Kurtarma yönergelerine bakın: https://aka.ms/wsldiskmountrecovery</value>
</data>
<data name="MessageFailedToTranslate" xml:space="preserve">
@ -1364,7 +1364,7 @@ Kurtarma yönergelerine bakın: https://aka.ms/wsldiskmountrecovery</value>
<value>Yoksayılan bağlantı noktaları</value>
</data>
<data name="Settings_IgnoredPorts.Description" xml:space="preserve">
<value>Yalnızca wsl2.networkingMode olarak ayarlandığında uygulanabilir. Linux uygulamalarının otomatik olarak iletilen veya Windows'ta değerlendirilen bağlantı noktalarına bağlanacak bağlantı noktalarını belirtir. Virgülle ayrılmış bir listede biçimlendirilmelidir, örneğin: 3000.9000.9090.</value>
<value>Yalnızca wsl2.networkingMode olarak ayarlandığında uygulanabilir. Linux uygulamalarının otomatik olarak iletilen veya Windows'da değerlendirilen bağlantı noktalarına bağlanacak bağlantı noktalarını belirtir. Virgülle ayrılmış bir listede biçimlendirilmelidir, örneğin: 3000.9000.9090.</value>
<comment>{Locked="wsl2.networkingMode"}.wslconfig property key names should not be translated</comment>
</data>
<data name="Settings_IgnoredPortsResetButton.Content" xml:space="preserve">

View File

@ -315,21 +315,17 @@ void GnsEngine::ProcessDNSChange(Interface& interface, const wsl::shared::hns::D
content << L"nameserver " << server << L"\n";
}
if (!payload.Domain.empty())
{
content << L"domain " << payload.Domain << L"\n";
}
// Use 'search' for DNS suffixes.
// Per resolv.conf(5): "The domain directive is an obsolete name for the search directive
// that handles one search list entry only."
// See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
if (!payload.Search.empty())
{
content << L"search " << wsl::shared::string::Join(wsl::shared::string::Split(payload.Search, L','), L' ') << L"\n";
}
GNS_LOG_INFO(
"Setting DNS server domain to {}: {} on interfaceName {} ",
payload.Domain.c_str(),
content.str().c_str(),
interface.Name().c_str());
"Setting DNS search to {}: {} on interfaceName {} ", payload.Search.c_str(), content.str().c_str(), interface.Name().c_str());
THROW_LAST_ERROR_IF(UtilMkdirPath("/etc", 0755) < 0);
std::wofstream resolvConf;

View File

@ -1729,10 +1729,6 @@ Return Value:
if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
{
MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
if (MountSource.empty())
{
continue;
}
}
else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
{
@ -1741,13 +1737,18 @@ Return Value:
}
else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
{
MountSource = UtilParseVirtiofsMountSource(MountEnum.Current().Source);
MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
}
else
{
continue;
}
if (MountSource.empty())
{
continue;
}
auto letter = ConfigGetDriveLetter(MountSource);
if (letter.has_value())
{
@ -2445,17 +2446,10 @@ try
NewMountOptions += ',';
}
MountPlan9Filesystem(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin, Config);
MountPlan9Share(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin, Config);
}
else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) == 0)
{
std::string_view Source = MountEntry.Source;
std::string_view OldTag = Message->Admin ? LX_INIT_DRVFS_VIRTIO_TAG : LX_INIT_DRVFS_ADMIN_VIRTIO_TAG;
if (!wsl::shared::string::StartsWith(Source, OldTag))
{
continue;
}
RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
}
else

View File

@ -298,70 +298,12 @@ try
{
return MountFilesystem(DRVFS_FS_TYPE, Source, Target, Options, ExitCode);
}
// Use virtiofs if the source of the mount is the root of a drive; otherwise, use 9p.
if (WSL_USE_VIRTIO_FS(Config))
else if (WSL_USE_VIRTIO_FS(Config))
{
if (wsl::shared::string::IsDriveRoot(Source))
{
return MountVirtioFs(Source, Target, Options, Admin, Config, ExitCode);
}
LOG_WARNING("virtiofs is only supported for mounting full drives, using 9p to mount {}", Source);
return MountVirtioFs(Source, Target, Options, Admin, Config, ExitCode);
}
//
// Check if the path is a UNC path.
//
const char* Plan9Source;
std::string UncSource;
if ((strlen(Source) >= PLAN9_UNC_PREFIX_LENGTH) && ((Source[0] == '/') || (Source[0] == '\\')) &&
((Source[1] == '/') || (Source[1] == '\\')))
{
UncSource = PLAN9_UNC_TRANSLATED_PREFIX;
UncSource += &Source[PLAN9_UNC_PREFIX_LENGTH];
Plan9Source = UncSource.c_str();
}
else
{
Plan9Source = Source;
}
//
// Check whether to use the elevated or regular 9p server.
//
bool Elevated = Admin.has_value() ? Admin.value() : IsDrvfsElevated();
//
// Initialize mount options.
//
auto Plan9Options = std::format("{};path={}", PLAN9_ANAME_DRVFS, Plan9Source);
//
// N.B. The cache option is added to the start of this so if the user
// specifies one explicitly, it will override the default.
//
std::string MountOptions = "cache=mmap,";
auto ParsedOptions = ConvertDrvfsMountOptionsToPlan9(Options ? Options : "", Config);
Plan9Options += ParsedOptions.first;
MountOptions += ParsedOptions.second;
//
// Append the 9p mount options to the end of the other mount options and perform the mount operation.
//
MountOptions += Plan9Options;
if (MountPlan9Filesystem(Source, Target, MountOptions.c_str(), Elevated, Config, ExitCode) < 0)
{
return -1;
}
return 0;
return MountPlan9(Source, Target, Options, Admin, Config, ExitCode);
}
CATCH_RETURN_ERRNO()
@ -407,7 +349,7 @@ Return Value:
return ExitCode;
}
int MountPlan9Filesystem(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
int MountPlan9Share(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
/*++
@ -425,6 +367,8 @@ Arguments:
Admin - Supplies a boolean specifying if the admin share should be used.
Config - Supplies the distribution configuration.
ExitCode - Supplies an optional pointer that receives the exit code.
Return Value:
@ -452,10 +396,95 @@ Return Value:
MountOptions =
std::format("msize={},trans=fd,rfdno={},wfdno={},{}", LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE, Fd.get(), Fd.get(), Options);
return MountFilesystem(PLAN9_FS_TYPE, Source, Target, MountOptions.c_str(), ExitCode);
}
}
int MountPlan9(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
/*++
Routine Description:
This routine will perform a DrvFs mount using Plan9.
Arguments:
Source - Supplies the mount source.
Target - Supplies the mount target.
Options - Supplies the mount options.
Admin - Supplies an optional boolean to specify if the admin or non-admin share should be used.
Config - Supplies the distribution configuration.
ExitCode - Supplies an optional pointer that receives the exit code.
Return Value:
0 on success, -1 on failure.
--*/
try
{
//
// Check if the path is a UNC path.
//
const char* Plan9Source;
std::string UncSource;
if ((strlen(Source) >= PLAN9_UNC_PREFIX_LENGTH) && ((Source[0] == '/') || (Source[0] == '\\')) &&
((Source[1] == '/') || (Source[1] == '\\')))
{
UncSource = PLAN9_UNC_TRANSLATED_PREFIX;
UncSource += &Source[PLAN9_UNC_PREFIX_LENGTH];
Plan9Source = UncSource.c_str();
}
else
{
Plan9Source = Source;
}
//
// Check whether to use the elevated or regular 9p server.
//
bool Elevated = Admin.has_value() ? Admin.value() : IsDrvfsElevated();
//
// Initialize mount options.
//
auto Plan9Options = std::format("{};path={}", PLAN9_ANAME_DRVFS, Plan9Source);
//
// N.B. The cache option is added to the start of this so if the user
// specifies one explicitly, it will override the default.
//
std::string MountOptions = "cache=mmap,";
auto ParsedOptions = ConvertDrvfsMountOptionsToPlan9(Options ? Options : "", Config);
Plan9Options += ParsedOptions.first;
MountOptions += ParsedOptions.second;
//
// Append the 9p mount options to the end of the other mount options and perform the mount operation.
//
MountOptions += Plan9Options;
if (MountPlan9Share(Source, Target, MountOptions.c_str(), Elevated, Config, ExitCode) < 0)
{
return -1;
}
return 0;
}
CATCH_RETURN_ERRNO()
int MountVirtioFs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode)
/*++
@ -486,8 +515,6 @@ Return Value:
try
{
assert(wsl::shared::string::IsDriveRoot(Source));
//
// Check whether to use the elevated or non-elevated virtiofs server.
//
@ -516,7 +543,7 @@ try
AddShare.WriteString(AddShare->OptionsOffset, Plan9Options);
//
// Connect to the wsl service to add the virtiofs share.
// Connect to the wsl service to add the virtiofs share. If adding the share fails, fallback to mounting using Plan9.
//
wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_VIRTIOFS_PORT, true), "VirtoFs"};
@ -527,11 +554,10 @@ try
gsl::span<gsl::byte> ResponseSpan;
const auto& Response = Channel.Transaction<LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE>(AddShare.Span(), &ResponseSpan);
if (Response.Result != 0)
{
LOG_ERROR("Add virtiofs share for {} failed {}", Source, Response.Result);
return -1;
LOG_WARNING("Add virtiofs share for {} failed {}, falling back to Plan9", Source, Response.Result);
return MountPlan9(Source, Target, Options, Admin, Config, ExitCode);
}
//
@ -596,3 +622,52 @@ try
return MountWithRetry(Tag, Target, VIRTIO_FS_TYPE, Options);
}
CATCH_RETURN_ERRNO()
std::string QueryVirtiofsMountSource(const char* Tag)
/*++
Routine Description:
This routine takes a virtiofs tag and determines the Windows path it refers to.
Arguments:
Tag - Supplies the virtiofs tag to query.
Return Value:
The mount source, an empty string on failure.
--*/
try
{
wsl::shared::MessageWriter<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE> QueryShare(LxInitMessageQueryVirtioFsDevice);
QueryShare.WriteString(QueryShare->TagOffset, Tag);
//
// Connect to the host and send the query request.
//
wsl::shared::SocketChannel Channel{UtilConnectVsock(LX_INIT_UTILITY_VM_VIRTIOFS_PORT, true), "QueryVirtioFs"};
if (Channel.Socket() < 0)
{
return {};
}
gsl::span<gsl::byte> ResponseSpan;
const auto& Response = Channel.Transaction<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(QueryShare.Span(), &ResponseSpan);
if (Response.Result != 0)
{
LOG_ERROR("Query virtiofs share for {} failed {}", Tag, Response.Result);
return {};
}
return wsl::shared::string::FromSpan(ResponseSpan, Response.TagOffset);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return {};
}

View File

@ -23,9 +23,12 @@ int MountDrvfs(const char* Source, const char* Target, const char* Options, std:
int MountDrvfsEntry(int Argc, char* Argv[]);
int MountPlan9Filesystem(
const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
int MountPlan9Share(const char* Source, const char* Target, const char* Options, bool Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
int MountPlan9(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode);
int MountVirtioFs(const char* Source, const char* Target, const char* Options, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config, int* ExitCode = nullptr);
int RemountVirtioFs(const char* Tag, const char* Target, const char* Options, bool Admin);
std::string QueryVirtiofsMountSource(const char* Tag);

View File

@ -904,11 +904,12 @@ try
}
else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
{
MountSource = UtilParseVirtiofsMountSource(MountEnum.Current().Source);
MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
if (MountSource.empty())
{
continue;
}
MountEnum.Current().Source = MountSource.data();
}
else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
@ -2047,41 +2048,6 @@ Return Value:
return {};
}
std::string UtilParseVirtiofsMountSource(std::string_view Source)
/*++
Routine Description:
This routine parses the mount source to determine the actual source of a
a VirtioFs mount.
Arguments:
Source - Supplies the source string.
Return Value:
The mount source, or NULL if the source is not valid.
--*/
{
std::string MountSource{};
if (wsl::shared::string::StartsWith(Source, LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) && (Source.size() >= sizeof(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG)))
{
MountSource = Source[sizeof(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) - 1];
MountSource += ":";
}
else if (wsl::shared::string::StartsWith(Source, LX_INIT_DRVFS_VIRTIO_TAG) && (Source.size() >= sizeof(LX_INIT_DRVFS_VIRTIO_TAG)))
{
MountSource = Source[sizeof(LX_INIT_DRVFS_VIRTIO_TAG) - 1];
MountSource += ":";
}
return MountSource;
}
std::vector<char> UtilParseWslEnv(char* NtEnvironment)
/*++

View File

@ -259,8 +259,6 @@ int UtilParseCgroupsLine(char* Line, char** SubsystemName, bool* Enabled);
std::string UtilParsePlan9MountSource(std::string_view MountOptions);
std::string UtilParseVirtiofsMountSource(std::string_view MountOptions);
std::vector<char> UtilParseWslEnv(char* NtEnvironment);
int UtilProcessChildExitCode(int Status, const char* Name, int ExpectedStatus = 0, bool PrintError = true);

View File

@ -444,7 +444,7 @@ Return Value:
constexpr auto Usage = std::bind(Localization::MessageWslPathUsage, Localization::Options::Default);
parser.AddPositionalArgument(OriginalPath, 0);
parser.AddArgument(SetFlag<int, TRANSLATE_FLAG_ABSOLUTE>{Flags}, nullptr, TRANSLATE_MODE_ABSOLUTE);
parser.AddArgument(SetFlag<TRANSLATE_FLAG_ABSOLUTE, int>{Flags}, nullptr, TRANSLATE_MODE_ABSOLUTE);
parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_UNIX>{Mode, Usage}, nullptr, TRANSLATE_MODE_UNIX);
parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_WINDOWS>{Mode, Usage}, nullptr, TRANSLATE_MODE_WINDOWS);
parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_MIXED>{Mode, Usage}, nullptr, TRANSLATE_MODE_MIXED);

View File

@ -44,7 +44,7 @@ struct Argument
bool Positional;
};
template <typename T, T Flag>
template <auto Flag, typename T = std::remove_reference_t<decltype(Flag)>>
struct SetFlag
{
T& value;
@ -55,6 +55,17 @@ struct SetFlag
}
};
template <auto Flag, typename T = std::remove_reference_t<decltype(Flag)>>
struct ClearFlag
{
T& value;
void operator()() const
{
WI_ClearFlag(value, Flag);
}
};
template <typename T>
struct is_optional : std::false_type
{

View File

@ -319,6 +319,7 @@ typedef enum _LX_MESSAGE_TYPE
LxInitMessageAddVirtioFsDevice,
LxInitMessageAddVirtioFsDeviceResponse,
LxInitMessageRemountVirtioFsDevice,
LxInitMessageQueryVirtioFsDevice,
LxInitMessageStartDistroInit,
LxInitMessageCreateLoginSession,
LxInitMessageStopPlan9Server,
@ -1169,6 +1170,17 @@ typedef struct _LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE
PRETTY_PRINT(FIELD(Header), FIELD(Admin), STRING_FIELD(TagOffset));
} LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE, *PLX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE;
typedef struct _LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE
{
static inline auto Type = LxInitMessageQueryVirtioFsDevice;
using TResponse = LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE;
MESSAGE_HEADER Header;
unsigned int TagOffset;
char Buffer[];
PRETTY_PRINT(FIELD(Header), STRING_FIELD(TagOffset));
} LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE, *PLX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE;
//
// The messages that can be sent to mini_init.
//

View File

@ -52,26 +52,6 @@ inline unsigned int CopyToSpan(const std::string_view String, const gsl::span<gs
return PreviousOffset;
}
inline bool IsDriveRoot(const std::string_view Path)
{
bool IsRoot = true;
if (Path.length() == 3)
{
IsRoot &= Path[2] == '\\';
}
if (Path.length() == 2 || Path.length() == 3)
{
IsRoot &= isalpha(Path[0]) && Path[1] == ':';
}
else
{
IsRoot = false;
}
return IsRoot;
}
template <class T>
inline bool EndsWith(const std::basic_string<T>& String, const std::basic_string_view<T> Suffix)
{

View File

@ -26,8 +26,13 @@ static wil::srwlock g_endpointsInUseLock;
static std::vector<GUID> g_endpointsInUse;
NatNetworking::NatNetworking(
HCS_SYSTEM system, wsl::windows::common::hcs::unique_hcn_network&& network, GnsChannel&& gnsChannel, Config& config, wil::unique_socket&& dnsHvsocket) :
m_system(system), m_config(config), m_network(std::move(network)), m_gnsChannel(std::move(gnsChannel))
HCS_SYSTEM system,
wsl::windows::common::hcs::unique_hcn_network&& network,
GnsChannel&& gnsChannel,
Config& config,
wil::unique_socket&& dnsHvsocket,
LPCWSTR dnsOptions) :
m_system(system), m_config(config), m_network(std::move(network)), m_dnsOptions(dnsOptions), m_gnsChannel(std::move(gnsChannel))
{
m_connectivityTelemetryEnabled = config.EnableTelemetry && !WslTraceLoggingShouldDisableTelemetry();
@ -48,7 +53,7 @@ NatNetworking::NatNetworking(
// prioritized means:
// - can only set 3 DNS servers (Linux limitation)
// - when there are multiple host connected interfaces, we need to use the DNS servers from the most-likely-to-be-used interface on the host
m_mirrorDnsInfo.emplace();
m_useMirrorDnsSettings = true;
}
}
@ -337,7 +342,7 @@ void NatNetworking::Initialize()
UpdateDns(endpointProperties.GatewayAddress.c_str());
// if using the shared access DNS proxy, ensure that the shared access service is allowed inbound UDP access.
if (!m_mirrorDnsInfo && !m_dnsTunnelingResolver)
if (!m_useMirrorDnsSettings && !m_dnsTunnelingResolver)
{
// N.B. This rule works around a host OS issue that prevents the DNS proxy from working on older versions of Windows.
ConfigureSharedAccessFirewallRule();
@ -433,35 +438,22 @@ _Requires_lock_held_(m_lock)
void NatNetworking::UpdateDns(std::optional<PCWSTR> gatewayAddress) noexcept
try
{
if (!m_dnsTunnelingResolver && !m_mirrorDnsInfo && !gatewayAddress)
if (!m_dnsTunnelingResolver && !m_useMirrorDnsSettings && !gatewayAddress)
{
return;
}
networking::DnsInfo latestDnsSettings{};
// true if the "domain" entry of /etc/resolv.conf should be configured
// Note: the "domain" entry allows a single DNS suffix to be configured
bool configureLinuxDomain = false;
// NAT mode with DNS tunneling
if (m_dnsTunnelingResolver)
{
latestDnsSettings = HostDnsInfo::GetDnsTunnelingSettings(m_dnsTunnelingIpAddress);
}
// NAT mode without Shared Access DNS proxy
else if (m_mirrorDnsInfo)
else if (m_useMirrorDnsSettings)
{
m_mirrorDnsInfo->UpdateNetworkInformation();
const auto settings = m_mirrorDnsInfo->GetDnsSettings(DnsSettingsFlags::IncludeVpn);
latestDnsSettings.Servers = std::move(settings.Servers);
if (!settings.Domains.empty())
{
latestDnsSettings.Domains.emplace_back(std::move(settings.Domains.front()));
configureLinuxDomain = true;
}
latestDnsSettings = HostDnsInfo::GetDnsSettings(DnsSettingsFlags::IncludeVpn);
}
// NAT mode with Shared Access DNS proxy
else if (gatewayAddress)
@ -472,11 +464,10 @@ try
if (latestDnsSettings != m_trackedDnsSettings)
{
auto dnsNotification = BuildDnsNotification(latestDnsSettings, configureLinuxDomain);
auto dnsNotification = BuildDnsNotification(latestDnsSettings, m_dnsOptions);
WSL_LOG(
"NatNetworking::UpdateDns",
TraceLoggingValue(dnsNotification.Domain.c_str(), "domain"),
TraceLoggingValue(dnsNotification.Options.c_str(), "options"),
TraceLoggingValue(dnsNotification.Search.c_str(), "search"),
TraceLoggingValue(dnsNotification.ServerList.c_str(), "serverList"));

View File

@ -18,7 +18,13 @@ namespace wsl::core {
class NatNetworking final : public INetworkingEngine
{
public:
NatNetworking(HCS_SYSTEM system, wsl::windows::common::hcs::unique_hcn_network&& network, GnsChannel&& gnsChannel, Config& config, wil::unique_socket&& dnsHvsocket);
NatNetworking(
HCS_SYSTEM system,
wsl::windows::common::hcs::unique_hcn_network&& network,
GnsChannel&& gnsChannel,
Config& config,
wil::unique_socket&& dnsHvsocket,
LPCWSTR dnsOptions = LX_INIT_RESOLVCONF_FULL_HEADER);
~NatNetworking() override;
// Note: This class cannot be moved because m_networkNotifyHandle captures a 'this' pointer.
@ -69,12 +75,18 @@ private:
// The latest DNS settings configured in Linux
_Guarded_by_(m_lock) networking::DnsInfo m_trackedDnsSettings {};
// If true, DNS settings are retrieved from host adapters (mirrored mode)
// rather than using the shared access DNS proxy
bool m_useMirrorDnsSettings = false;
// Options/header for /etc/resolv.conf
LPCWSTR m_dnsOptions = nullptr;
GnsChannel m_gnsChannel;
std::shared_ptr<networking::NetworkSettings> m_networkSettings;
networking::EphemeralHcnEndpoint m_endpoint;
ULONG m_networkMtu = 0;
std::optional<networking::HostDnsInfo> m_mirrorDnsInfo;
networking::unique_notify_handle m_networkNotifyHandle{};
};

View File

@ -14,11 +14,12 @@ using wsl::core::VirtioNetworking;
static constexpr auto c_loopbackDeviceName = TEXT(LX_INIT_LOOPBACK_DEVICE_NAME);
VirtioNetworking::VirtioNetworking(
GnsChannel&& gnsChannel, bool enableLocalhostRelay, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken) :
GnsChannel&& gnsChannel, bool enableLocalhostRelay, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken) :
m_guestDeviceManager(std::move(guestDeviceManager)),
m_userToken(std::move(userToken)),
m_gnsChannel(std::move(gnsChannel)),
m_enableLocalhostRelay(enableLocalhostRelay)
m_enableLocalhostRelay(enableLocalhostRelay),
m_dnsOptions(dnsOptions)
{
}
@ -66,7 +67,7 @@ void VirtioNetworking::Initialize()
}
// Get initial DNS settings for device options.
auto initialDns = m_dnsUpdateHelper.GetCurrentDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
auto initialDns = networking::HostDnsInfo::GetDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
if (!initialDns.Servers.empty())
{
if (device_options.tellp() > 0)
@ -260,7 +261,7 @@ try
UpdateMtu();
// Check for DNS changes and send update if needed.
auto currentDns = m_dnsUpdateHelper.GetCurrentDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
auto currentDns = networking::HostDnsInfo::GetDnsSettings(networking::DnsSettingsFlags::IncludeVpn);
if (currentDns != m_trackedDnsSettings)
{
m_trackedDnsSettings = currentDns;
@ -274,7 +275,7 @@ void VirtioNetworking::SendDnsUpdate(const networking::DnsInfo& dnsSettings)
hns::ModifyGuestEndpointSettingRequest<hns::DNS> notification{};
notification.RequestType = hns::ModifyRequestType::Update;
notification.ResourceType = hns::GuestEndpointResourceType::DNS;
notification.Settings = networking::BuildDnsNotification(dnsSettings);
notification.Settings = networking::BuildDnsNotification(dnsSettings, m_dnsOptions);
m_gnsChannel.SendHnsNotification(ToJsonW(notification).c_str(), m_adapterId);
}

View File

@ -13,7 +13,7 @@ namespace wsl::core {
class VirtioNetworking : public INetworkingEngine
{
public:
VirtioNetworking(GnsChannel&& gnsChannel, bool enableLocalhostRelay, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken);
VirtioNetworking(GnsChannel&& gnsChannel, bool enableLocalhostRelay, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken);
~VirtioNetworking();
// Note: This class cannot be moved because m_networkNotifyHandle captures a 'this' pointer.
@ -49,12 +49,12 @@ private:
std::optional<GnsPortTrackerChannel> m_gnsPortTrackerChannel;
std::shared_ptr<networking::NetworkSettings> m_networkSettings;
bool m_enableLocalhostRelay;
LPCWSTR m_dnsOptions = nullptr;
GUID m_localhostAdapterId;
GUID m_adapterId;
std::optional<ULONGLONG> m_interfaceLuid;
ULONG m_networkMtu = 0;
networking::DnsUpdateHelper m_dnsUpdateHelper;
networking::DnsInfo m_trackedDnsSettings;
// Note: this field must be destroyed first to stop the callbacks before any other field is destroyed.

View File

@ -135,7 +135,7 @@ void wsl::windows::common::WSLAContainerLauncher::AddVolume(const std::wstring&
m_volumes.push_back(vol);
}
std::pair<HRESULT, std::optional<RunningWSLAContainer>> WSLAContainerLauncher::LaunchNoThrow(IWSLASession& Session)
std::pair<HRESULT, std::optional<RunningWSLAContainer>> WSLAContainerLauncher::LaunchNoThrow(IWSLASession& Session, WSLAContainerStartFlags Flags)
{
auto [result, container] = CreateNoThrow(Session);
if (FAILED(result))
@ -143,7 +143,7 @@ std::pair<HRESULT, std::optional<RunningWSLAContainer>> WSLAContainerLauncher::L
return std::make_pair(result, std::optional<RunningWSLAContainer>{});
}
result = container.value().Get().Start();
result = container.value().Get().Start(Flags);
return std::make_pair(result, std::move(container));
}
@ -207,9 +207,9 @@ std::pair<HRESULT, std::optional<RunningWSLAContainer>> WSLAContainerLauncher::C
return std::make_pair(S_OK, std::move(RunningWSLAContainer{std::move(container), m_flags}));
}
RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session)
RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session, WSLAContainerStartFlags Flags)
{
auto [result, container] = LaunchNoThrow(Session);
auto [result, container] = LaunchNoThrow(Session, Flags);
THROW_IF_FAILED(result);
return std::move(container.value());

View File

@ -60,8 +60,8 @@ public:
std::pair<HRESULT, std::optional<RunningWSLAContainer>> CreateNoThrow(IWSLASession& Session);
RunningWSLAContainer Launch(IWSLASession& Session);
std::pair<HRESULT, std::optional<RunningWSLAContainer>> LaunchNoThrow(IWSLASession& Session);
RunningWSLAContainer Launch(IWSLASession& Session, WSLAContainerStartFlags Flags = WSLAContainerStartFlagsAttach);
std::pair<HRESULT, std::optional<RunningWSLAContainer>> LaunchNoThrow(IWSLASession& Session, WSLAContainerStartFlags Flags = WSLAContainerStartFlagsAttach);
void SetEntrypoint(std::vector<std::string>&& entrypoint);
void SetDefaultStopSignal(WSLASignal Signal);

View File

@ -74,6 +74,11 @@ RunningWSLAProcess::RunningWSLAProcess(WSLAProcessFlags Flags) : m_flags(Flags)
{
}
WSLAProcessFlags RunningWSLAProcess::Flags() const
{
return m_flags;
}
int RunningWSLAProcess::GetExitCode()
{
WSLA_PROCESS_STATE state{};

View File

@ -42,6 +42,8 @@ public:
int GetExitCode();
WSLA_PROCESS_STATE State();
WSLAProcessFlags Flags() const;
protected:
virtual void GetState(WSLA_PROCESS_STATE* State, int* Code) = 0;

View File

@ -255,7 +255,7 @@ int ExportDistribution(_In_ std::wstring_view commandLine)
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(SetFlag<LXSS_EXPORT_DISTRO_FLAGS_VHD, ULONG>(flags), WSL_EXPORT_ARG_VHD_OPTION);
parser.AddArgument(parseFormat, WSL_EXPORT_ARG_FORMAT_OPTION);
parser.Parse();
@ -321,7 +321,7 @@ int ImportDistribution(_In_ std::wstring_view commandLine)
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.AddArgument(SetFlag<LXSS_IMPORT_DISTRO_FLAGS_VHD, ULONG>{flags}, WSL_IMPORT_ARG_VHD);
parser.Parse();
@ -1543,12 +1543,9 @@ int WslaShell(_In_ std::wstring_view commandLine)
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
parser.AddArgument(rootVhdOverride, L"--vhd");
parser.AddArgument(Utf8String(shell), L"--shell");
parser.AddArgument(
SetFlag<int, WslaFeatureFlagsDnsTunneling>(reinterpret_cast<int&>(sessionSettings.FeatureFlags)), L"--dns-tunneling");
parser.AddArgument(
SetFlag<int, WslaFeatureFlagsPmemVhds>(reinterpret_cast<int&>(sessionSettings.FeatureFlags)), L"--pmem-vhds");
parser.AddArgument(
SetFlag<int, WslaFeatureFlagsVirtioFs>(reinterpret_cast<int&>(sessionSettings.FeatureFlags)), L"--virtiofs");
parser.AddArgument(SetFlag<WslaFeatureFlagsDnsTunneling>(sessionSettings.FeatureFlags), L"--dns-tunneling");
parser.AddArgument(SetFlag<WslaFeatureFlagsPmemVhds>(sessionSettings.FeatureFlags), L"--pmem-vhds");
parser.AddArgument(SetFlag<WslaFeatureFlagsVirtioFs>(sessionSettings.FeatureFlags), L"--virtiofs");
parser.AddArgument(Integer(sessionSettings.MemoryMb), L"--memory");
parser.AddArgument(Integer(sessionSettings.CpuCount), L"--cpu");
parser.AddArgument(Utf8String(rootVhdTypeOverride), L"--fstype");

View File

@ -105,12 +105,6 @@ wsl::core::networking::DnsInfo wsl::core::networking::HostDnsInfo::GetDnsTunneli
return dnsInfo;
}
std::vector<wsl::core::networking::IpAdapterAddress> wsl::core::networking::HostDnsInfo::GetAdapterAddresses()
{
std::lock_guard<std::mutex> lock(m_lock);
return m_addresses;
}
std::vector<std::string> wsl::core::networking::HostDnsInfo::GetDnsServerStrings(
_In_ const PIP_ADAPTER_DNS_SERVER_ADDRESS& FirstDnsServer, _In_ USHORT IpFamilyFilter, _In_ USHORT MaxValues)
{
@ -233,7 +227,7 @@ std::vector<std::string> wsl::core::networking::HostDnsInfo::GetInterfaceDnsSuff
wsl::core::networking::DnsInfo wsl::core::networking::HostDnsInfo::GetDnsSettings(_In_ DnsSettingsFlags Flags)
{
std::vector<IpAdapterAddress> Addresses = GetAdapterAddresses();
std::vector<IpAdapterAddress> Addresses = AdapterAddresses::GetCurrent();
auto RemoveFilter = [&](const IpAdapterAddress& Address) {
// Ignore interfaces that are not currently "up".
@ -326,12 +320,6 @@ wsl::core::networking::DnsInfo wsl::core::networking::HostDnsInfo::GetDnsSetting
return DnsSettings;
}
void wsl::core::networking::HostDnsInfo::UpdateNetworkInformation()
{
std::lock_guard<std::mutex> lock(m_lock);
m_addresses = AdapterAddresses::GetCurrent();
}
std::string wsl::core::networking::GenerateResolvConf(_In_ const DnsInfo& Info)
{
std::string contents{};
@ -345,7 +333,10 @@ std::string wsl::core::networking::GenerateResolvConf(_In_ const DnsInfo& Info)
contents += c_asciiNewLine;
}
// Add domain information if it is available.
// Add DNS suffix information using 'search' directive.
// Per resolv.conf(5): "The domain directive is an obsolete name for the search directive
// that handles one search list entry only."
// See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
if (!Info.Domains.empty())
{
contents += "search ";
@ -497,28 +488,21 @@ wsl::core::networking::DnsSuffixRegistryWatcher::DnsSuffixRegistryWatcher(Regist
m_registryWatchers.swap(localRegistryWatchers);
}
wsl::shared::hns::DNS wsl::core::networking::BuildDnsNotification(const DnsInfo& settings, bool useLinuxDomainEntry)
wsl::shared::hns::DNS wsl::core::networking::BuildDnsNotification(const DnsInfo& settings, PCWSTR options)
{
wsl::shared::hns::DNS dnsNotification{};
dnsNotification.Options = LX_INIT_RESOLVCONF_FULL_HEADER;
if (options)
{
dnsNotification.Options = options;
}
dnsNotification.ServerList = wsl::shared::string::MultiByteToWide(wsl::shared::string::Join(settings.Servers, ','));
if (useLinuxDomainEntry && !settings.Domains.empty())
{
// Use 'domain' entry for single DNS suffix (typically used when mirroring host DNS without tunneling)
dnsNotification.Domain = wsl::shared::string::MultiByteToWide(settings.Domains.front());
}
else
{
// Use 'search' entry for DNS suffix list
dnsNotification.Search = wsl::shared::string::MultiByteToWide(wsl::shared::string::Join(settings.Domains, ','));
}
// Use 'search' entry for DNS suffix list.
// Per resolv.conf(5): "The domain directive is an obsolete name for the search directive
// that handles one search list entry only."
// See: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
dnsNotification.Search = wsl::shared::string::MultiByteToWide(wsl::shared::string::Join(settings.Domains, ','));
return dnsNotification;
}
wsl::core::networking::DnsInfo wsl::core::networking::DnsUpdateHelper::GetCurrentDnsSettings(DnsSettingsFlags flags)
{
m_hostDnsInfo.UpdateNetworkInformation();
return m_hostDnsInfo.GetDnsSettings(flags);
}

View File

@ -1,7 +1,6 @@
// Copyright (C) Microsoft Corporation. All rights reserved.
#pragma once
#include <mutex>
#include <string>
#include <vector>
@ -42,9 +41,9 @@ std::string GenerateResolvConf(_In_ const DnsInfo& Info);
/// Builds an hns::DNS notification from DnsInfo settings.
/// </summary>
/// <param name="settings">The DNS settings to convert</param>
/// <param name="useLinuxDomainEntry">If true, uses 'domain' entry for single suffix; otherwise uses 'search' for all
/// suffixes</param> <returns>The hns::DNS notification ready to send via GNS channel</returns>
wsl::shared::hns::DNS BuildDnsNotification(const DnsInfo& settings, bool useLinuxDomainEntry = false);
/// <param name="options">The resolv.conf header options (defaults to LX_INIT_RESOLVCONF_FULL_HEADER)</param>
/// <returns>The hns::DNS notification ready to send via GNS channel</returns>
wsl::shared::hns::DNS BuildDnsNotification(const DnsInfo& settings, PCWSTR options = LX_INIT_RESOLVCONF_FULL_HEADER);
std::vector<std::string> GetAllDnsSuffixes(const std::vector<IpAdapterAddress>& AdapterAddresses);
@ -53,27 +52,15 @@ DWORD GetBestInterface();
class HostDnsInfo
{
public:
DnsInfo GetDnsSettings(_In_ DnsSettingsFlags Flags);
void UpdateNetworkInformation();
static DnsInfo GetDnsSettings(_In_ DnsSettingsFlags Flags);
static DnsInfo GetDnsTunnelingSettings(const std::wstring& dnsTunnelingNameserver);
const std::vector<IpAdapterAddress>& CurrentAddresses() const
{
return m_addresses;
}
private:
/// <summary>
/// Internal function to retrieve the latest copy of interface information.
/// </summary>
std::vector<IpAdapterAddress> GetAdapterAddresses();
/// <summary>
/// Internal function to retrieve interface DNS servers.
/// </summary>
std::vector<std::string> GetInterfaceDnsServers(const std::vector<IpAdapterAddress>& AdapterAddresses, _In_ DnsSettingsFlags Flags);
static std::vector<std::string> GetInterfaceDnsServers(const std::vector<IpAdapterAddress>& AdapterAddresses, _In_ DnsSettingsFlags Flags);
/// <summary>
/// Internal function to retrieve all Windows DNS suffixes.
@ -84,30 +71,6 @@ private:
/// Internal function to convert DNS server addresses into strings.
/// </summary>
static std::vector<std::string> GetDnsServerStrings(_In_ const PIP_ADAPTER_DNS_SERVER_ADDRESS& DnsServer, _In_ USHORT IpFamilyFilter, _In_ USHORT MaxValues);
/// <summary>
/// Stores latest copy of interface information.
/// </summary>
std::mutex m_lock;
_Guarded_by_(m_lock) std::vector<IpAdapterAddress> m_addresses;
};
/// <summary>
/// Helper class that fetches current DNS settings from the host.
/// Callers are responsible for tracking changes if needed.
/// </summary>
class DnsUpdateHelper
{
public:
/// <summary>
/// Fetches current DNS settings from the host.
/// </summary>
/// <param name="flags">Flags controlling which DNS settings to include</param>
/// <returns>Current DNS settings</returns>
DnsInfo GetCurrentDnsSettings(DnsSettingsFlags flags);
private:
HostDnsInfo m_hostDnsInfo;
};
using RegistryChangeCallback = std::function<void()>;

View File

@ -974,6 +974,7 @@ bool MultiHandleWait::Run(std::optional<std::chrono::milliseconds> Timeout)
if (WI_IsFlagSet(m_handles[i].first, Flags::IgnoreErrors))
{
m_handles[i].second.reset(); // Reset the handle so it can be deleted.
break;
}
else
{

View File

@ -784,9 +784,6 @@ try
// Impersonate the service.
auto runAsSelf = wil::run_as_self();
// Update the instance's DNS information.
m_dnsInfo.UpdateNetworkInformation();
// Update the resolv.conf file if it has changed.
_UpdateNetworkConfigurationFiles(false);
return;
@ -799,7 +796,7 @@ void LxssInstance::_UpdateNetworkConfigurationFiles(_In_ bool UpdateAlways)
wsl::core::networking::DnsSettingsFlags flags = wsl::core::networking::DnsSettingsFlags::IncludeIpv6Servers;
WI_SetFlagIf(flags, wsl::core::networking::DnsSettingsFlags::IncludeVpn, m_enableVpnDetection);
const auto dnsSettings = m_dnsInfo.GetDnsSettings(flags);
const auto dnsSettings = wsl::core::networking::HostDnsInfo::GetDnsSettings(flags);
std::string fileContents = GenerateResolvConf(dnsSettings);
std::lock_guard<std::mutex> lock(m_resolvConfLock);
if (!UpdateAlways && (fileContents == m_lastResolvConfContents))

View File

@ -224,11 +224,6 @@ private:
/// </summary>
LxssIpTables m_ipTables;
/// <summary>
/// Class for querying host dns information.
/// </summary>
wsl::core::networking::HostDnsInfo m_dnsInfo;
/// <summary>
/// Settings for updating /etc/resolv.conf.
/// </summary>

View File

@ -570,7 +570,7 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
else if (m_vmConfig.NetworkingMode == NetworkingMode::VirtioProxy)
{
m_networkingEngine = std::make_unique<wsl::core::VirtioNetworking>(
std::move(gnsChannel), m_vmConfig.EnableLocalhostRelay, m_guestDeviceManager, m_userToken);
std::move(gnsChannel), m_vmConfig.EnableLocalhostRelay, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken);
}
else if (m_vmConfig.NetworkingMode == NetworkingMode::Bridged)
{
@ -845,9 +845,6 @@ void WslCoreVm::AddDrvFsShare(_In_ bool Admin, _In_ HANDLE UserToken)
{
// Add virtiofs devices associating indices with paths from the fixed drive bitmap. These devices support
// multiple mounts in the guest, so this only needs to be done once.
// EX: drvfsC1 => C:\
// drvfsD2 => D:\
// drvfsaC3 => C:\ (elevated)
auto fixedDrives = wsl::windows::common::filesystem::EnumerateFixedDrives(UserToken).first;
while (fixedDrives != 0)
{
@ -2084,7 +2081,7 @@ void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId)
_Requires_lock_held_(m_guestDeviceLock)
std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
{
WI_ASSERT(m_vmConfig.EnableVirtioFs && wsl::shared::string::IsDriveRoot(wsl::shared::string::WideToMultiByte(Path)));
WI_ASSERT(m_vmConfig.EnableVirtioFs);
if (!ARGUMENT_PRESENT(UserToken))
{
@ -2095,22 +2092,27 @@ std::wstring WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_
WI_ASSERT(Admin == wsl::windows::common::security::IsTokenElevated(UserToken));
// Ensure that the path has a trailing path separator.
std::wstring sharePath{Path};
if (sharePath.back() != L'\\')
std::wstring sharePath(Path);
if (!sharePath.ends_with(L'\\') && !sharePath.ends_with(L'/'))
{
sharePath += L'\\';
sharePath.push_back(L'\\');
}
sharePath = std::filesystem::weakly_canonical(sharePath).wstring();
// Check if a matching share already exists.
bool created = false;
std::wstring tag;
VirtioFsShare key(sharePath.c_str(), Options, Admin);
if (!m_virtioFsShares.contains(key))
{
// Generate a new tag for the share.
tag = Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG);
tag += sharePath[0];
tag += std::to_wstring(m_virtioFsShares.size());
// Generate a new unique tag for the share.
//
// N.B. The tag can be maximum 36 characters long so a GUID without braces fits perfectly.
GUID tagGuid{};
THROW_IF_FAILED(CoCreateGuid(&tagGuid));
tag = wsl::shared::string::GuidToString<wchar_t>(tagGuid, wsl::shared::string::None);
WI_ASSERT(!FindVirtioFsShare(tag.c_str(), Admin));
(void)m_guestDeviceManager->AddGuestDevice(
@ -2546,8 +2548,6 @@ try
THROW_HR_IF(E_UNEXPECTED, !addShare);
const auto path = wsl::shared::string::FromSpan(span, addShare->PathOffset);
THROW_HR_IF_MSG(E_INVALIDARG, !wsl::shared::string::IsDriveRoot(path), "%hs is not the root of a drive", path);
const auto pathWide = wsl::shared::string::MultiByteToWide(path);
const auto options = wsl::shared::string::FromSpan(span, addShare->OptionsOffset);
const auto optionsWide = wsl::shared::string::MultiByteToWide(options);
@ -2567,19 +2567,6 @@ try
THROW_HR_IF(E_UNEXPECTED, !remountShare);
const std::string tag = wsl::shared::string::FromSpan(span, remountShare->TagOffset);
if (tag.find(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG, 0) == 0)
{
THROW_HR_IF(E_UNEXPECTED, remountShare->Admin);
}
else if (tag.find(LX_INIT_DRVFS_VIRTIO_TAG, 0) == 0)
{
THROW_HR_IF(E_UNEXPECTED, !remountShare->Admin);
}
else
{
THROW_HR_MSG(E_UNEXPECTED, "Unexpected tag %hs", tag.data());
}
const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
const auto foundShare = FindVirtioFsShare(tagWide.c_str(), !remountShare->Admin);
@ -2590,6 +2577,24 @@ try
respondWithTag(newTag, result);
}
else if (message->MessageType == LxInitMessageQueryVirtioFsDevice)
{
std::wstring newTag;
const auto result = wil::ResultFromException([this, span, &newTag]() {
const auto* query = gslhelpers::try_get_struct<LX_INIT_QUERY_VIRTIOFS_SHARE_MESSAGE>(span);
THROW_HR_IF(E_UNEXPECTED, !query);
const std::string tag = wsl::shared::string::FromSpan(span, query->TagOffset);
const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
const auto foundShare = FindVirtioFsShare(tagWide.c_str());
THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
newTag = foundShare->Path;
});
respondWithTag(newTag, result);
}
else
{
THROW_HR_MSG(E_UNEXPECTED, "Unexpected MessageType %d", message->MessageType);

View File

@ -434,8 +434,7 @@ void wsl::core::networking::WslMirroredNetworkManager::ProcessDNSChange()
}
else
{
m_hostDnsInfo.UpdateNetworkInformation();
m_dnsInfo = m_hostDnsInfo.GetDnsSettings(
m_dnsInfo = wsl::core::networking::HostDnsInfo::GetDnsSettings(
wsl::core::networking::DnsSettingsFlags::IncludeVpn | wsl::core::networking::DnsSettingsFlags::IncludeIpv6Servers |
wsl::core::networking::DnsSettingsFlags::IncludeAllSuffixes);
}

View File

@ -224,9 +224,6 @@ private:
_Guarded_by_(m_networkLock) DnsInfo m_trackedDnsInfo;
// The current DNS info on the host
_Guarded_by_(m_networkLock) DnsInfo m_dnsInfo;
// m_hostDnsInfo is an optimization used to avoid allocating a large buffer every time we call
// GetAdaptersAddresses when querying host DNS info
_Guarded_by_(m_networkLock) HostDnsInfo m_hostDnsInfo;
std::wstring m_dnsTunnelingIpAddress;

View File

@ -416,7 +416,7 @@ static int Logs(std::wstring_view commandLine)
std::string id;
WSLALogsFlags flags = WSLALogsFlagsNone;
parser.AddPositionalArgument(Utf8String{id}, 0);
parser.AddArgument(SetFlag<WSLALogsFlags, WSLALogsFlagsFollow>(flags), L"--follow", 'f');
parser.AddArgument(SetFlag<WSLALogsFlagsFollow>(flags), L"--follow", 'f');
parser.Parse();
THROW_HR_IF(E_INVALIDARG, id.empty());
@ -500,24 +500,27 @@ static void RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_handle&&
}
});
// Required because ReadFile() blocks if stdin is a tty.
if (wsl::windows::common::wslutil::IsInteractiveConsole())
if (Stdin.is_valid())
{
// TODO: Will output CR instead of LF's which can confuse the linux app.
// Consider a custom relay logic to fix this.
inputThread = std::thread{[&]() {
try
{
wsl::windows::common::relay::InterruptableRelay(GetStdHandle(STD_INPUT_HANDLE), Stdin.get(), exitEvent.get());
}
CATCH_LOG();
// Required because ReadFile() blocks if stdin is a tty.
if (wsl::windows::common::wslutil::IsInteractiveConsole())
{
// TODO: Will output CR instead of LF's which can confuse the linux app.
// Consider a custom relay logic to fix this.
inputThread = std::thread{[&]() {
try
{
wsl::windows::common::relay::InterruptableRelay(GetStdHandle(STD_INPUT_HANDLE), Stdin.get(), exitEvent.get());
}
CATCH_LOG();
Stdin.reset();
}};
}
else
{
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(GetStdHandle(STD_INPUT_HANDLE), std::move(Stdin)));
Stdin.reset();
}};
}
else
{
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(GetStdHandle(STD_INPUT_HANDLE), std::move(Stdin)));
}
}
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(std::move(Stdout), GetStdHandle(STD_OUTPUT_HANDLE)));
@ -526,15 +529,21 @@ static void RelayNonTtyProcess(wil::unique_handle&& Stdin, wil::unique_handle&&
io.Run({});
}
static int InteractiveShell(ClientRunningWSLAProcess&& Process, bool Tty)
static int InteractiveShell(ClientRunningWSLAProcess&& Process)
{
if (Tty)
if (WI_IsFlagSet(Process.Flags(), WSLAProcessFlagsTty))
{
RelayInteractiveTty(Process, Process.GetStdHandle(WSLAFDTty).get());
}
else
{
RelayNonTtyProcess(Process.GetStdHandle(WSLAFDStdin), Process.GetStdHandle(WSLAFDStdout), Process.GetStdHandle(WSLAFDStderr));
wil::unique_handle stdinHandle;
if (WI_IsFlagSet(Process.Flags(), WSLAProcessFlagsStdin))
{
stdinHandle = Process.GetStdHandle(WSLAFDStdin);
}
RelayNonTtyProcess(std::move(stdinHandle), Process.GetStdHandle(WSLAFDStdout), Process.GetStdHandle(WSLAFDStderr));
}
return Process.Wait();
@ -544,13 +553,15 @@ static int Run(std::wstring_view commandLine)
{
ArgumentParser parser(std::wstring{commandLine}, L"wsladiag", 2, true);
bool interactive{};
bool tty{};
WSLA_CONTAINER_OPTIONS options{};
WSLAContainerStartFlags startFlags = WSLAContainerStartFlagsAttach;
std::string image;
std::string name;
parser.AddPositionalArgument(Utf8String{image}, 0);
parser.AddArgument(interactive, L"--interactive", 'i');
parser.AddArgument(tty, L"--tty", 't');
parser.AddArgument(SetFlag<WSLAProcessFlagsStdin>{options.InitProcessOptions.Flags}, L"--interactive", 'i');
parser.AddArgument(SetFlag<WSLAProcessFlagsTty>{options.InitProcessOptions.Flags}, L"--tty", 't');
parser.AddArgument(ClearFlag<WSLAContainerStartFlagsAttach>{startFlags}, L"--detach", 'd');
parser.AddArgument(Utf8String{name}, L"--name");
parser.Parse();
@ -558,26 +569,20 @@ static int Run(std::wstring_view commandLine)
auto session = OpenCLISession();
WSLA_CONTAINER_OPTIONS options{};
options.Image = image.c_str();
HANDLE Stdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE);
if (tty)
if (WI_IsFlagSet(options.InitProcessOptions.Flags, WSLAProcessFlagsTty))
{
CONSOLE_SCREEN_BUFFER_INFOEX Info{};
Info.cbSize = sizeof(Info);
THROW_IF_WIN32_BOOL_FALSE(::GetConsoleScreenBufferInfoEx(Stdout, &Info));
options.InitProcessOptions.Flags = WSLAProcessFlagsTty | WSLAProcessFlagsStdin;
options.InitProcessOptions.TtyColumns = Info.srWindow.Right - Info.srWindow.Left + 1;
options.InitProcessOptions.TtyRows = Info.srWindow.Bottom - Info.srWindow.Top + 1;
}
else
{
WI_SetFlagIf(options.InitProcessOptions.Flags, WSLAProcessFlagsStdin, interactive);
}
std::vector<std::string> argsStorage;
std::vector<const char*> args;
@ -613,12 +618,24 @@ static int Run(std::wstring_view commandLine)
error.ThrowIfFailed(result);
THROW_IF_FAILED(container->Start()); // TODO: Error message
THROW_IF_FAILED(container->Start(startFlags)); // TODO: Error message
wil::com_ptr<IWSLAProcess> process;
THROW_IF_FAILED(container->GetInitProcess(&process));
if (WI_IsFlagSet(startFlags, WSLAContainerStartFlagsAttach))
{
wil::com_ptr<IWSLAProcess> process;
THROW_IF_FAILED(container->GetInitProcess(&process));
return InteractiveShell(ClientRunningWSLAProcess(std::move(process), options.InitProcessOptions.Flags), tty);
return InteractiveShell(ClientRunningWSLAProcess(std::move(process), options.InitProcessOptions.Flags));
}
else
{
WSLAContainerId containerId{};
THROW_IF_FAILED(container->GetId(containerId));
wslutil::PrintMessage(L"%hs\n", stdout, containerId);
return 0;
}
}
static int Attach(std::wstring_view commandLine)

View File

@ -3,7 +3,7 @@ set(SOURCES
ContainerEventTracker.cpp
DockerHTTPClient.cpp
main.rc
LogsRelay.cpp
IORelay.cpp
ServiceMain.cpp
ServiceProcessLauncher.cpp
WSLAContainer.cpp
@ -20,7 +20,7 @@ set(HEADERS
COMImplClass.h
ContainerEventTracker.h
DockerHTTPClient.h
LogsRelay.h
IORelay.h
ServiceProcessLauncher.h
WSLAContainer.h
WSLAProcess.h

View File

@ -53,36 +53,44 @@ ContainerEventTracker::ContainerTrackingReference::~ContainerTrackingReference()
Reset();
}
ContainerEventTracker::ContainerEventTracker(DockerHTTPClient& dockerClient)
ContainerEventTracker::ContainerEventTracker(DockerHTTPClient& dockerClient, ULONG sessionId, IORelay& relay) :
m_sessionId(sessionId)
{
auto onChunk = [this](const gsl::span<char>& buffer) {
if (!buffer.empty()) // docker inserts empty lines between events, skip those.
{
try
{
OnEvent(std::string_view(buffer.data(), buffer.size()));
}
catch (...)
{
WSL_LOG(
"DockerEventParseError",
TraceLoggingValue(buffer.data(), "Data"),
TraceLoggingValue(wil::ResultFromCaughtException(), "Error"),
TraceLoggingValue(m_sessionId, "SessionId"));
}
}
};
auto socket = dockerClient.MonitorEvents();
m_thread = std::thread([socket = std::move(socket), this]() mutable { Run(std::move(socket)); }
);
}
void ContainerEventTracker::Stop()
{
// N.B. No callback should be left when the tracker is destroyed.
m_stopEvent.SetEvent();
if (m_thread.joinable())
{
m_thread.join();
}
relay.AddHandle(std::make_unique<common::relay::HTTPChunkBasedReadHandle>(std::move(socket), std::move(onChunk)));
}
ContainerEventTracker::~ContainerEventTracker()
{
// N.B. No callback should be left when the tracker is destroyed.
WI_ASSERT(m_callbacks.empty());
Stop();
}
void ContainerEventTracker::OnEvent(const std::string& event)
void ContainerEventTracker::OnEvent(const std::string_view& event)
{
// TODO: log session ID
WSL_LOG("DockerEvent", TraceLoggingValue(event.c_str(), "Data"));
WSL_LOG(
"DockerEvent",
TraceLoggingCountedString(event.data(), static_cast<UINT16>(event.size()), "Data"),
TraceLoggingValue(m_sessionId, "SessionId"));
static std::map<std::string, ContainerEvent> events{
{"start", ContainerEvent::Start}, {"die", ContainerEvent::Stop}, {"exec_die", ContainerEvent::ExecDied}};
@ -92,7 +100,12 @@ void ContainerEventTracker::OnEvent(const std::string& event)
auto action = parsed.find("Action");
auto actor = parsed.find("Actor");
THROW_HR_IF_MSG(E_INVALIDARG, action == parsed.end() || actor == parsed.end(), "Failed to parse json: %hs", event.c_str());
THROW_HR_IF_MSG(
E_INVALIDARG,
action == parsed.end() || actor == parsed.end(),
"Failed to parse json: %.*hs",
static_cast<int>(event.size()),
event.data());
auto it = events.find(action->get<std::string>());
if (it == events.end())
@ -101,7 +114,7 @@ void ContainerEventTracker::OnEvent(const std::string& event)
}
auto id = actor->find("ID");
THROW_HR_IF_MSG(E_INVALIDARG, id == actor->end(), "Failed to parse json: %hs", event.c_str());
THROW_HR_IF_MSG(E_INVALIDARG, id == actor->end(), "Failed to parse json: %.*hs", static_cast<int>(event.size()), event.data());
auto containerId = id->get<std::string>();
@ -134,43 +147,6 @@ void ContainerEventTracker::OnEvent(const std::string& event)
}
}
void ContainerEventTracker::Run(wil::unique_socket&& socket)
try
{
wsl::windows::common::relay::MultiHandleWait io;
auto oneLineWritten = [&](const gsl::span<char>& buffer) {
// docker events' output is line based. Call OnEvent() for each completed line.
if (!buffer.empty()) // docker inserts empty lines between events, skip those.
{
try
{
OnEvent(std::string{buffer.begin(), buffer.end()});
}
catch (...)
{
WSL_LOG(
"DockerEventParseError",
TraceLoggingValue(buffer.data(), "Data"),
TraceLoggingValue(wil::ResultFromCaughtException(), "Error"));
}
}
};
io.AddHandle(
std::make_unique<common::relay::HTTPChunkBasedReadHandle>(wil::unique_handle{(HANDLE)socket.release()}, std::move(oneLineWritten)),
MultiHandleWait::CancelOnCompleted);
io.AddHandle(std::make_unique<common::relay::EventHandle>(m_stopEvent.get()), MultiHandleWait::CancelOnCompleted);
if (io.Run({}))
{
// TODO: Report error to session.
WSL_LOG("Unexpected docker exit");
}
}
CATCH_LOG();
ContainerEventTracker::ContainerTrackingReference ContainerEventTracker::RegisterContainerStateUpdates(
const std::string& ContainerId, ContainerStateChangeCallback&& Callback)
{

View File

@ -11,9 +11,11 @@ Abstract:
Contains the definition for ContainerEventTracker.
--*/
#pragma once
#include "DockerHTTPClient.h"
#include "IORelay.h"
namespace wsl::windows::service::wsla {
@ -54,7 +56,7 @@ public:
using ContainerStateChangeCallback = std::function<void(ContainerEvent, std::optional<int>)>;
ContainerEventTracker(DockerHTTPClient& dockerClient);
ContainerEventTracker(DockerHTTPClient& dockerClient, ULONG sessionId, IORelay& relay);
~ContainerEventTracker();
void Stop();
@ -64,7 +66,7 @@ public:
void UnregisterContainerStateUpdates(size_t Id);
private:
void OnEvent(const std::string& event);
void OnEvent(const std::string_view& event);
void Run(wil::unique_socket&& Socket);
struct Callback
@ -77,8 +79,7 @@ private:
std::vector<Callback> m_callbacks;
std::thread m_thread;
wil::unique_event m_stopEvent{wil::EventOptions::ManualReset};
ULONG m_sessionId{};
std::recursive_mutex m_lock;
std::atomic<size_t> m_callbackId{0};
};

View File

@ -30,9 +30,30 @@ Abstract:
namespace http = boost::beast::http;
using boost::beast::http::verb;
using wsl::windows::common::relay::HandleWrapper;
using wsl::windows::common::relay::MultiHandleWait;
using wsl::windows::service::wsla::DockerHTTPClient;
using namespace wsl::windows::common;
namespace {
bool IsResponseChunked(const http::response_parser<http::buffer_body>::value_type& response)
{
auto transferEncoding = response.find(http::field::transfer_encoding);
if (transferEncoding == response.end())
{
return false;
}
if (transferEncoding->value() != "chunked")
{
THROW_HR_MSG(E_UNEXPECTED, "Unknown transfer encoding: %hs", std::string(transferEncoding->value()).c_str());
}
return true;
}
} // namespace
DockerHTTPClient::DockerHTTPClient(wsl::shared::SocketChannel&& Channel, HANDLE exitingEvent, GUID VmId, ULONG ConnectTimeoutMs) :
m_exitingEvent(exitingEvent), m_channel(std::move(Channel)), m_vmId(VmId), m_connectTimeoutMs(ConnectTimeoutMs)
{
@ -143,7 +164,7 @@ void DockerHTTPClient::DeleteContainer(const std::string& Id)
std::string DockerHTTPClient::InspectContainer(const std::string& Id)
{
auto url = std::format("http://localhost/containers/{}/json", Id);
auto [code, response] = SendRequest(verb::get, url);
auto [code, response] = SendRequestAndReadResponse(verb::get, url);
if (code < 200 || code >= 300)
{
@ -159,7 +180,7 @@ wil::unique_socket DockerHTTPClient::AttachContainer(const std::string& Id)
{boost::beast::http::field::upgrade, "tcp"}, {boost::beast::http::field::connection, "upgrade"}};
auto url = std::format("http://localhost/containers/{}/attach?stream=1&stdin=1&stdout=1&stderr=1", Id);
auto [status, socket] = SendRequest(verb::post, url, {}, {}, headers);
auto [status, socket] = SendRequest(verb::post, url, {}, headers);
if (status != 101)
{
@ -219,7 +240,7 @@ wil::unique_socket DockerHTTPClient::StartExec(const std::string& Id, const comm
auto url = std::format("http://localhost/exec/{}/start", Id);
auto body = wsl::shared::ToJson(Request);
auto [status, socket] = SendRequest(verb::post, url, body, {}, headers);
auto [status, socket] = SendRequest(verb::post, url, body, headers);
if (status != 101)
{
throw DockerHTTPException(status, verb::post, url, body, "");
@ -271,14 +292,25 @@ wil::unique_socket DockerHTTPClient::ConnectSocket()
return newChannel.Release();
}
std::pair<uint32_t, std::string> DockerHTTPClient::SendRequest(verb Method, const std::string& Url, const std::string& Body)
std::pair<uint32_t, std::string> DockerHTTPClient::SendRequestAndReadResponse(verb Method, const std::string& Url, const std::string& Body)
{
// Send the request.
auto context = SendRequestImpl(Method, Url, Body, {});
// Read the response header and body.
std::optional<boost::beast::http::status> status;
std::string responseBody;
auto OnResponse = [&responseBody](const gsl::span<char>& span) { responseBody.append(span.data(), span.size()); };
auto [status, _] = SendRequest(Method, Url, Body, OnResponse);
auto onHttpResponse = [&](const auto& response) { status = response.result(); };
MultiHandleWait io;
return {status, std::move(responseBody)};
io.AddHandle(std::make_unique<relay::EventHandle>(m_exitingEvent, [&]() { THROW_HR(E_ABORT); }));
io.AddHandle(std::make_unique<DockerHttpResponseHandle>(*context, std::move(onHttpResponse), std::move(OnResponse)), MultiHandleWait::CancelOnCompleted);
io.Run({});
return {static_cast<uint32_t>(status.value()), responseBody};
}
DockerHTTPClient::DockerHttpResponseHandle::DockerHttpResponseHandle(
@ -339,9 +371,7 @@ void DockerHTTPClient::DockerHttpResponseHandle::OnRead(const gsl::span<char>& C
OnResponseHeader(response);
// If the response is chunked, then create a chunked reader.
// TODO: Proper header parsing.
auto transferEncoding = response.find(http::field::transfer_encoding);
if (transferEncoding != response.end() && transferEncoding->value() == "chunked")
if (IsResponseChunked(response))
{
ResponseParser.emplace(HandleWrapper{Context.stream.native_handle()}, std::move(OnResponse));
}
@ -432,11 +462,7 @@ std::unique_ptr<DockerHTTPClient::HTTPRequestContext> DockerHTTPClient::SendRequ
}
std::pair<uint32_t, wil::unique_socket> DockerHTTPClient::SendRequest(
verb Method,
const std::string& Url,
const std::string& Body,
const OnResponseBytes& OnResponse,
const std::map<boost::beast::http::field, std::string>& Headers)
verb Method, const std::string& Url, const std::string& Body, const std::map<boost::beast::http::field, std::string>& Headers)
{
// Write the request
auto context = SendRequestImpl(Method, Url, Body, Headers);
@ -504,24 +530,5 @@ std::pair<uint32_t, wil::unique_socket> DockerHTTPClient::SendRequest(
}
}
WSL_LOG("HTTPResult", TraceLoggingValue(Url.c_str(), "Url"), TraceLoggingValue(parser.get().result_int(), "Status"));
if (OnResponse)
{
buffer.resize(bufferSize);
while (!parser.is_done())
{
boost::beast::flat_buffer adapter;
parser.get().body().data = buffer.data();
parser.get().body().size = buffer.size();
http::read(context->stream, adapter, parser);
auto bytesRead = buffer.size() - parser.get().body().size;
OnResponse(gsl::span<char>{buffer.data(), bytesRead});
}
}
return {parser.get().result_int(), wil::unique_socket{context->stream.release()}};
}

View File

@ -116,7 +116,7 @@ public:
HTTPRequestContext& context,
std::function<void(const boost::beast::http::message<false, boost::beast::http::buffer_body>&)>&& OnResponseHeader,
std::function<void(const gsl::span<char>&)>&& OnResponseBytes,
std::function<void()>&& OnCompleted);
std::function<void()>&& OnCompleted = []() {});
~DockerHttpResponseHandle();
@ -140,14 +140,13 @@ private:
std::unique_ptr<HTTPRequestContext> SendRequestImpl(
boost::beast::http::verb Method, const std::string& Url, const std::string& Body, const std::map<boost::beast::http::field, std::string>& Headers);
std::pair<uint32_t, std::string> SendRequest(
std::pair<uint32_t, std::string> SendRequestAndReadResponse(
boost::beast::http::verb Method, const std::string& Url, const std::string& Body = "");
std::pair<uint32_t, wil::unique_socket> SendRequest(
boost::beast::http::verb Method,
const std::string& Url,
const std::string& Body,
const OnResponseBytes& OnResponse,
const std::map<boost::beast::http::field, std::string>& Headers = {});
template <typename TRequest = common::docker_schema::EmptyRequest, typename TResponse = TRequest::TResponse>
@ -159,7 +158,7 @@ private:
requestString = wsl::shared::ToJson(RequestObject);
}
auto [statusCode, responseString] = SendRequest(Method, Url, requestString);
auto [statusCode, responseString] = SendRequestAndReadResponse(Method, Url, requestString);
if (statusCode < 200 || statusCode >= 300)
{

View File

@ -0,0 +1,96 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
IORelay.cpp
Abstract:
Contains the implementation of the IORelay class.
--*/
#include "IORelay.h"
using wsl::windows::common::relay::DockerIORelayHandle;
using wsl::windows::common::relay::MultiHandleWait;
using wsl::windows::common::relay::OverlappedIOHandle;
using wsl::windows::service::wsla::IORelay;
IORelay::IORelay()
{
m_thread = std::thread([this]() { Run(); });
}
IORelay::~IORelay()
{
Stop();
}
void IORelay::AddHandle(std::unique_ptr<common::relay::OverlappedIOHandle>&& Handle)
{
std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>> handles;
handles.emplace_back(std::move(Handle));
AddHandles(std::move(handles));
}
void IORelay::AddHandles(std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>>&& Handles)
{
WI_ASSERT(!m_exit);
std::lock_guard lock(m_pendingHandlesLock);
// Append the new handles
// N.B. IgnoreErrors is set so the IO doesn't stop on individual handle errors.
for (auto& e : Handles)
{
WI_ASSERT(!!e);
m_pendingHandles.emplace_back(std::move(e));
}
// Restart the relay thread.
m_refreshEvent.SetEvent();
}
void IORelay::Stop()
{
m_exit = true;
m_refreshEvent.SetEvent();
if (m_thread.joinable())
{
m_thread.join();
}
}
void IORelay::Run()
try
{
common::wslutil::SetThreadDescription(L"IORelay");
windows::common::relay::MultiHandleWait io;
// N.B. All the IO must happen on the thread.
// If the thread that scheduled the IO exits, the IO is cancelled.
while (!m_exit)
{
{
// Add any pending handles.
std::lock_guard lock(m_pendingHandlesLock);
for (auto& e : m_pendingHandles)
{
io.AddHandle(std::move(e), MultiHandleWait::IgnoreErrors);
}
m_pendingHandles.clear();
}
io.AddHandle(std::make_unique<common::relay::EventHandle>(m_refreshEvent.get()), MultiHandleWait::CancelOnCompleted);
io.Run({});
}
}
CATCH_LOG();

View File

@ -0,0 +1,45 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
IORelay.h
Abstract:
Contains the definition of the IORelay class.
--*/
#pragma once
namespace wsl::windows::service::wsla {
class IORelay
{
public:
NON_COPYABLE(IORelay);
IORelay();
~IORelay();
void AddHandles(std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>>&& Handles);
void AddHandle(std::unique_ptr<common::relay::OverlappedIOHandle>&& Handle);
void Stop();
private:
void Start();
void Run();
std::mutex m_pendingHandlesLock;
wil::unique_event m_refreshEvent{wil::EventOptions::None};
std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>> m_pendingHandles;
std::thread m_thread;
bool m_exit = false;
};
} // namespace wsl::windows::service::wsla

View File

@ -1,75 +0,0 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LogsRelay.cpp
Abstract:
Contains the implementation of the LogsRelay class.
--*/
#include "LogsRelay.h"
using wsl::windows::common::relay::DockerIORelayHandle;
using wsl::windows::common::relay::MultiHandleWait;
using wsl::windows::common::relay::OverlappedIOHandle;
using wsl::windows::service::wsla::LogsRelay;
LogsRelay::~LogsRelay()
{
StopRelayThread();
}
void LogsRelay::AddHandle(std::unique_ptr<common::relay::OverlappedIOHandle>&& Handle)
{
std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>> handles;
handles.emplace_back(std::move(Handle));
AddHandles(std::move(handles));
}
void LogsRelay::AddHandles(std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>>&& Handles)
{
// Stop the relay thread.
StopRelayThread();
// Append the new handles
// N.B. IgnoreErrors is set so the IO doesn't stop on individual handle errors.
for (auto& e : Handles)
{
m_io.AddHandle(std::move(e), MultiHandleWait::IgnoreErrors);
}
// Restart the relay thread.
StartRelayThread();
}
void LogsRelay::StartRelayThread()
{
WI_ASSERT(!m_thread.joinable());
m_stopEvent.ResetEvent();
m_thread = std::thread([this]() { Run(); });
}
void LogsRelay::StopRelayThread()
{
if (m_thread.joinable())
{
m_stopEvent.SetEvent();
m_thread.join();
}
}
void LogsRelay::Run()
try
{
m_io.AddHandle(std::make_unique<common::relay::EventHandle>(m_stopEvent.get()), MultiHandleWait::CancelOnCompleted);
m_io.Run({});
}
CATCH_LOG();

View File

@ -1,41 +0,0 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LogsRelay.h
Abstract:
Contains the definition of the LogsRelay class.
--*/
#pragma once
namespace wsl::windows::service::wsla {
class LogsRelay
{
public:
NON_COPYABLE(LogsRelay);
LogsRelay() = default;
~LogsRelay();
void AddHandles(std::vector<std::unique_ptr<common::relay::OverlappedIOHandle>>&& Handles);
void AddHandle(std::unique_ptr<common::relay::OverlappedIOHandle>&& Handle);
private:
void StartRelayThread();
void StopRelayThread();
void Run();
std::thread m_thread;
windows::common::relay::MultiHandleWait m_io;
wil::unique_event m_stopEvent{wil::EventOptions::ManualReset};
};
} // namespace wsl::windows::service::wsla

View File

@ -23,6 +23,7 @@ using wsl::windows::common::relay::HTTPChunkBasedReadHandle;
using wsl::windows::common::relay::OverlappedIOHandle;
using wsl::windows::common::relay::ReadHandle;
using wsl::windows::common::relay::RelayHandle;
using wsl::windows::service::wsla::RelayedProcessIO;
using wsl::windows::service::wsla::VolumeMountInfo;
using wsl::windows::service::wsla::WSLAContainer;
using wsl::windows::service::wsla::WSLAContainerImpl;
@ -171,6 +172,7 @@ WSLAContainerImpl::WSLAContainerImpl(
std::function<void(const WSLAContainerImpl*)>&& onDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient,
IORelay& Relay,
WSLA_CONTAINER_STATE InitialState,
WSLAProcessFlags InitProcessFlags,
WSLAContainerFlags ContainerFlags) :
@ -183,6 +185,7 @@ WSLAContainerImpl::WSLAContainerImpl(
m_comWrapper(wil::MakeOrThrow<WSLAContainer>(this, std::move(onDeleted))),
m_dockerClient(DockerClient),
m_eventTracker(EventTracker),
m_ioRelay(Relay),
m_containerEvents(EventTracker.RegisterContainerStateUpdates(
m_id, std::bind(&WSLAContainerImpl::OnEvent, this, std::placeholders::_1, std::placeholders::_2))),
m_state(InitialState),
@ -303,20 +306,21 @@ void WSLAContainerImpl::Attach(ULONG* Stdin, ULONG* Stdout, ULONG* Stderr)
// This is required for docker to know when stdin is closed.
auto onInputComplete = [handle = ioHandle.get()]() { LOG_LAST_ERROR_IF(shutdown(handle, SD_SEND) == SOCKET_ERROR); };
// N.B. Ownership of the io handle is given to the DockerIORelayHandle relay, so it can be closed when docker closes the connection.
handles.emplace_back(
std::make_unique<RelayHandle<ReadHandle>>(HandleWrapper{std::move(stdinRead), std::move(onInputComplete)}, ioHandle.get()));
handles.emplace_back(std::make_unique<DockerIORelayHandle>(
ioHandle.get(), std::move(stdoutWrite), std::move(stderrWrite), DockerIORelayHandle::Format::Raw));
std::move(ioHandle), std::move(stdoutWrite), std::move(stderrWrite), DockerIORelayHandle::Format::Raw));
handles.emplace_back(std::make_unique<RelayHandle<ReadHandle>>(
HandleWrapper{std::move(stdinRead), std::move(onInputComplete)}, std::move(ioHandle)));
m_ioRelay.AddHandles(std::move(handles));
m_logsRelay.AddHandles(std::move(handles));
*Stdin = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stdinWrite.get()), GENERIC_WRITE));
*Stdout = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stdoutRead.get()), GENERIC_READ));
*Stderr = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stderrRead.get()), GENERIC_READ));
*Stdin = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stdinWrite.get())));
*Stdout = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stdoutRead.get())));
*Stderr = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(stderrRead.get())));
}
void WSLAContainerImpl::Start()
void WSLAContainerImpl::Start(WSLAContainerStartFlags Flags)
{
std::lock_guard<std::recursive_mutex> lock(m_lock);
@ -329,13 +333,18 @@ void WSLAContainerImpl::Start()
// Attach to the container's init process so no IO is lost.
std::unique_ptr<WSLAProcessIO> io;
if (WI_IsFlagSet(m_initProcessFlags, WSLAProcessFlagsTty))
if (WI_IsFlagSet(Flags, WSLAContainerStartFlagsAttach))
{
io = std::make_unique<TTYProcessIO>(wil::unique_handle{(HANDLE)m_dockerClient.AttachContainer(m_id).release()});
}
else
{
io = std::make_unique<RelayedProcessIO>(wil::unique_handle{(HANDLE)m_dockerClient.AttachContainer(m_id).release()});
if (WI_IsFlagSet(m_initProcessFlags, WSLAProcessFlagsTty))
{
io = std::make_unique<TTYProcessIO>(wil::unique_handle{(HANDLE)m_dockerClient.AttachContainer(m_id).release()});
}
else
{
wil::unique_handle stream{reinterpret_cast<HANDLE>(m_dockerClient.AttachContainer(m_id).release())};
io = CreateRelayedProcessIO(std::move(stream), m_initProcessFlags);
}
}
auto control = std::make_unique<DockerContainerProcessControl>(*this, m_dockerClient, m_eventTracker);
@ -535,7 +544,7 @@ void WSLAContainerImpl::Exec(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess**
}
else
{
io = std::make_unique<RelayedProcessIO>(std::move(stream));
io = CreateRelayedProcessIO(std::move(stream), Options->Flags);
}
auto control = std::make_unique<DockerExecProcessControl>(*this, result.Id, m_dockerClient, m_eventTracker);
@ -594,12 +603,15 @@ std::unique_ptr<WSLAContainerImpl> WSLAContainerImpl::Create(
WSLAVirtualMachine& parentVM,
std::function<void(const WSLAContainerImpl*)>&& OnDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient)
DockerHTTPClient& DockerClient,
IORelay& IoRelay)
{
// TODO: Think about when 'StdinOnce' should be set.
common::docker_schema::CreateContainer request;
request.Image = containerOptions.Image;
// TODO: Think about when 'StdinOnce' should be set.
request.StdinOnce = true;
if (WI_IsFlagSet(containerOptions.InitProcessOptions.Flags, WSLAProcessFlagsTty))
{
request.Tty = true;
@ -608,7 +620,6 @@ std::unique_ptr<WSLAContainerImpl> WSLAContainerImpl::Create(
if (WI_IsFlagSet(containerOptions.InitProcessOptions.Flags, WSLAProcessFlagsStdin))
{
request.OpenStdin = true;
request.StdinOnce = true;
}
request.Cmd = StringArrayToVector(containerOptions.InitProcessOptions.CommandLine);
@ -697,6 +708,7 @@ std::unique_ptr<WSLAContainerImpl> WSLAContainerImpl::Create(
std::move(OnDeleted),
EventTracker,
DockerClient,
IoRelay,
WslaContainerStateCreated,
containerOptions.InitProcessOptions.Flags,
containerOptions.Flags);
@ -711,7 +723,8 @@ std::unique_ptr<WSLAContainerImpl> WSLAContainerImpl::Open(
WSLAVirtualMachine& parentVM,
std::function<void(const WSLAContainerImpl*)>&& OnDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient)
DockerHTTPClient& DockerClient,
IORelay& ioRelay)
{
// Extract container name from Docker's names list.
std::string name = ExtractContainerName(dockerContainer.Names, dockerContainer.Id);
@ -743,6 +756,7 @@ std::unique_ptr<WSLAContainerImpl> WSLAContainerImpl::Open(
std::move(OnDeleted),
EventTracker,
DockerClient,
ioRelay,
WslaContainerStateExited,
WSLAProcessFlagsNone, // TODO
WSLAContainerFlagsNone);
@ -800,7 +814,7 @@ void WSLAContainerImpl::Logs(WSLALogsFlags Flags, ULONG* Stdout, ULONG* Stderr,
auto [ttyRead, ttyWrite] = common::wslutil::OpenAnonymousPipe(0, true, true);
auto handle = std::make_unique<RelayHandle<HTTPChunkBasedReadHandle>>(std::move(socket), std::move(ttyWrite));
m_logsRelay.AddHandle(std::move(handle));
m_ioRelay.AddHandle(std::move(handle));
*Stdout = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(ttyRead.get()));
}
@ -813,13 +827,52 @@ void WSLAContainerImpl::Logs(WSLALogsFlags Flags, ULONG* Stdout, ULONG* Stderr,
auto handle = std::make_unique<DockerIORelayHandle>(
std::move(socket), std::move(stdoutWrite), std::move(stderrWrite), DockerIORelayHandle::Format::HttpChunked);
m_logsRelay.AddHandle(std::move(handle));
m_ioRelay.AddHandle(std::move(handle));
*Stdout = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(stdoutRead.get()));
*Stderr = HandleToULong(common::wslutil::DuplicateHandleToCallingProcess(stderrRead.get()));
}
}
std::unique_ptr<RelayedProcessIO> WSLAContainerImpl::CreateRelayedProcessIO(wil::unique_handle&& stream, WSLAProcessFlags flags)
{
// Create one pipe for each STD handle.
std::vector<std::unique_ptr<OverlappedIOHandle>> ioHandles;
std::map<ULONG, wil::unique_handle> fds;
// This is required for docker to know when stdin is closed.
auto closeStdin = [socket = stream.get(), this]() {
LOG_LAST_ERROR_IF(shutdown(reinterpret_cast<SOCKET>(socket), SD_SEND) == SOCKET_ERROR);
};
if (WI_IsFlagSet(flags, WSLAProcessFlagsStdin))
{
auto [stdinRead, stdinWrite] = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
ioHandles.emplace_back(
std::make_unique<RelayHandle<ReadHandle>>(HandleWrapper{std::move(stdinRead), std::move(closeStdin)}, stream.get()));
fds.emplace(WSLAFDStdin, stdinWrite.release());
}
else
{
// If stdin is not attached, close it now to make sure no one tries to write to it.
closeStdin();
}
auto [stdoutRead, stdoutWrite] = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
auto [stderrRead, stderrWrite] = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
fds.emplace(WSLAFDStdout, stdoutRead.release());
fds.emplace(WSLAFDStderr, stderrRead.release());
ioHandles.emplace_back(std::make_unique<DockerIORelayHandle>(
std::move(stream), std::move(stdoutWrite), std::move(stderrWrite), common::relay::DockerIORelayHandle::Format::Raw));
m_ioRelay.AddHandles(std::move(ioHandles));
return std::make_unique<RelayedProcessIO>(std::move(fds));
}
WSLAContainer::WSLAContainer(WSLAContainerImpl* impl, std::function<void(const WSLAContainerImpl*)>&& OnDeleted) :
COMImplClass<WSLAContainerImpl>(impl), m_onDeleted(std::move(OnDeleted))
{
@ -857,9 +910,9 @@ HRESULT WSLAContainer::Stop(_In_ WSLASignal Signal, _In_ LONGLONG TimeoutSeconds
return CallImpl(&WSLAContainerImpl::Stop, Signal, TimeoutSeconds);
}
HRESULT WSLAContainer::Start()
HRESULT WSLAContainer::Start(WSLAContainerStartFlags Flags)
{
return CallImpl(&WSLAContainerImpl::Start);
return CallImpl(&WSLAContainerImpl::Start, Flags);
}
HRESULT WSLAContainer::Inspect(LPSTR* Output)

View File

@ -20,7 +20,7 @@ Abstract:
#include "ContainerEventTracker.h"
#include "DockerHTTPClient.h"
#include "WSLAProcessControl.h"
#include "LogsRelay.h"
#include "IORelay.h"
#include "COMImplClass.h"
namespace wsl::windows::service::wsla {
@ -60,13 +60,14 @@ public:
std::function<void(const WSLAContainerImpl*)>&& OnDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient,
IORelay& Relay,
WSLA_CONTAINER_STATE InitialState,
WSLAProcessFlags InitProcessFlags,
WSLAContainerFlags ContainerFlags);
~WSLAContainerImpl();
void Start();
void Start(WSLAContainerStartFlags Flags);
void Attach(ULONG* Stdin, ULONG* Stdout, ULONG* Stderr);
void Stop(_In_ WSLASignal Signal, _In_ LONGLONG TimeoutSeconds);
@ -93,18 +94,21 @@ public:
WSLAVirtualMachine& parentVM,
std::function<void(const WSLAContainerImpl*)>&& OnDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient);
DockerHTTPClient& DockerClient,
IORelay& Relay);
static std::unique_ptr<WSLAContainerImpl> Open(
const common::docker_schema::ContainerInfo& DockerContainer,
WSLAVirtualMachine& parentVM,
std::function<void(const WSLAContainerImpl*)>&& OnDeleted,
ContainerEventTracker& EventTracker,
DockerHTTPClient& DockerClient);
DockerHTTPClient& DockerClient,
IORelay& Relay);
private:
void OnEvent(ContainerEvent event, std::optional<int> exitCode);
void WaitForContainerEvent();
std::unique_ptr<RelayedProcessIO> CreateRelayedProcessIO(wil::unique_handle&& stream, WSLAProcessFlags flags);
std::recursive_mutex m_lock;
std::string m_name;
@ -123,7 +127,7 @@ private:
DockerContainerProcessControl* m_initProcessControl = nullptr;
ContainerEventTracker& m_eventTracker;
ContainerEventTracker::ContainerTrackingReference m_containerEvents;
LogsRelay m_logsRelay;
IORelay& m_ioRelay;
static std::vector<VolumeMountInfo> MountVolumes(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM);
static void UnmountVolumes(const std::vector<VolumeMountInfo>& volumes, WSLAVirtualMachine& parentVM);
@ -143,7 +147,7 @@ public:
IFACEMETHOD(GetState)(_Out_ WSLA_CONTAINER_STATE* State) override;
IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override;
IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override;
IFACEMETHOD(Start)() override;
IFACEMETHOD(Start)(WSLAContainerStartFlags Flags) override;
IFACEMETHOD(Inspect)(_Out_ LPSTR* Output) override;
IFACEMETHOD(GetID)(_Out_ LPSTR* Output) override;
IFACEMETHOD(Logs)(_In_ WSLALogsFlags Flags, _Out_ ULONG* Stdout, _Out_ ULONG* Stderr, _In_ ULONGLONG Since, _In_ ULONGLONG Until, _In_ ULONGLONG Tail) override;

View File

@ -42,6 +42,8 @@ CATCH_RETURN();
HRESULT WSLAProcess::GetStdHandle(ULONG Index, ULONG* Handle)
try
{
RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), !m_io, "Process IO not attached");
auto handle = m_io->OpenFd(Index);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !handle.is_valid());
@ -60,6 +62,8 @@ CATCH_RETURN();
wil::unique_handle WSLAProcess::GetStdHandle(int Index)
{
THROW_WIN32_IF(ERROR_INVALID_STATE, !m_io);
return m_io->OpenFd(Index);
}

View File

@ -21,72 +21,15 @@ using wsl::windows::service::wsla::TTYProcessIO;
using wsl::windows::service::wsla::VMProcessIO;
using namespace wsl::windows::common::relay;
RelayedProcessIO::RelayedProcessIO(wil::unique_handle&& IoStream) : m_ioStream(std::move(IoStream))
RelayedProcessIO::RelayedProcessIO(std::map<ULONG, wil::unique_handle>&& fds) : m_relayedHandles(std::move(fds))
{
}
RelayedProcessIO::~RelayedProcessIO()
{
if (m_thread.joinable())
{
m_exitEvent.SetEvent();
m_thread.join();
}
}
void RelayedProcessIO::StartIORelay()
{
WI_ASSERT(!m_thread.joinable() && !m_relayedHandles.has_value());
m_relayedHandles.emplace();
auto stdinPipe = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
auto stdoutPipe = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
auto stderrPipe = common::wslutil::OpenAnonymousPipe(LX_RELAY_BUFFER_SIZE, true, true);
m_relayedHandles->emplace(std::make_pair(WSLAFDStdin, stdinPipe.second.release()));
m_relayedHandles->emplace(std::make_pair(WSLAFDStdout, stdoutPipe.first.release()));
m_relayedHandles->emplace(std::make_pair(WSLAFDStderr, stderrPipe.first.release()));
m_thread = std::thread([this,
stdinPipe = std::move(stdinPipe.first),
stdoutPipe = std::move(stdoutPipe.second),
stderrPipe = std::move(stderrPipe.second)]() mutable {
RunIORelay(std::move(stdinPipe), std::move(stdoutPipe), std::move(stderrPipe));
});
}
void RelayedProcessIO::RunIORelay(wil::unique_hfile&& stdinPipe, wil::unique_hfile&& stdoutPipe, wil::unique_hfile&& stderrPipe)
try
{
common::relay::MultiHandleWait io;
// This is required for docker to know when stdin is closed.
auto onInputComplete = [&]() {
LOG_LAST_ERROR_IF(shutdown(reinterpret_cast<SOCKET>(m_ioStream.get()), SD_SEND) == SOCKET_ERROR);
};
io.AddHandle(std::make_unique<RelayHandle<ReadHandle>>(
common::relay::HandleWrapper{std::move(stdinPipe), std::move(onInputComplete)}, m_ioStream.get()));
io.AddHandle(std::make_unique<EventHandle>(m_exitEvent.get()), MultiHandleWait::CancelOnCompleted);
io.AddHandle(std::make_unique<DockerIORelayHandle>(
m_ioStream.get(), std::move(stdoutPipe), std::move(stderrPipe), common::relay::DockerIORelayHandle::Format::Raw));
io.Run({});
}
CATCH_LOG();
wil::unique_handle RelayedProcessIO::OpenFd(ULONG Fd)
{
if (!m_relayedHandles.has_value())
{
StartIORelay();
}
auto it = m_relayedHandles.find(Fd);
auto it = m_relayedHandles->find(Fd);
THROW_HR_IF_MSG(E_INVALIDARG, it == m_relayedHandles->end(), "Fd not found in relayed handles: %i", static_cast<int>(Fd));
THROW_HR_IF_MSG(E_INVALIDARG, it == m_relayedHandles.end(), "Fd not found in relayed handles: %i", static_cast<int>(Fd));
THROW_WIN32_IF_MSG(ERROR_INVALID_STATE, !it->second.is_valid(), "Fd already consumed: %i", static_cast<int>(Fd));
return std::move(it->second);

View File

@ -27,19 +27,12 @@ public:
class RelayedProcessIO : public WSLAProcessIO
{
public:
RelayedProcessIO(wil::unique_handle&& IoStream);
virtual ~RelayedProcessIO();
RelayedProcessIO(std::map<ULONG, wil::unique_handle>&& fds);
wil::unique_handle OpenFd(ULONG Fd) override;
private:
void RunIORelay(wil::unique_hfile&& stdinPipe, wil::unique_hfile&& stdoutPipe, wil::unique_hfile&& stderrPipe);
void StartIORelay();
std::thread m_thread;
wil::unique_handle m_ioStream;
wil::unique_event m_exitEvent{wil::EventOptions::ManualReset};
std::optional<std::map<ULONG, wil::unique_handle>> m_relayedHandles;
std::map<ULONG, wil::unique_handle> m_relayedHandles;
};
class TTYProcessIO : public WSLAProcessIO

View File

@ -60,8 +60,11 @@ bool IsContainerNameValid(LPCSTR Name)
} // namespace
WSLASession::WSLASession(ULONG id, const WSLA_SESSION_SETTINGS& Settings, wil::unique_tokeninfo_ptr<TOKEN_USER>&& TokenInfo, bool Elevated) :
m_id(id), m_displayName(Settings.DisplayName), m_tokenInfo(std::move(TokenInfo)), m_elevatedToken(Elevated)
m_id(id),
m_displayName(Settings.DisplayName),
m_tokenInfo(std::move(TokenInfo)),
m_elevatedToken(Elevated),
m_featureFlags(Settings.FeatureFlags)
{
auto callingProcess = wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION);
m_creatorPid = GetProcessId(callingProcess.get());
@ -85,33 +88,21 @@ WSLASession::WSLASession(ULONG id, const WSLA_SESSION_SETTINGS& Settings, wil::u
ConfigureStorage(Settings, m_tokenInfo->User.Sid);
// Make sure that everything is destroyed correctly if an exception is thrown.
auto errorCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
m_sessionTerminatingEvent.SetEvent();
auto errorCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_FAILED(Terminate()); });
if (m_containerdThread.joinable())
{
m_containerdThread.join();
}
});
// Launch dockerd
StartDockerd();
// Launch containerd
// TODO: Rework the daemon logic so we can have only one thread watching all daemons.
ServiceProcessLauncher launcher{
"/usr/bin/dockerd",
{"/usr/bin/dockerd" /*, "--debug"*/}, // TODO: Flag for --debug.
{{"PATH=/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin"}}};
m_containerdThread = std::thread(&WSLASession::MonitorContainerd, this, launcher.Launch(*m_virtualMachine));
// Wait for containerd to be ready before starting the event tracker.
// TODO: Configurable timeout.
THROW_WIN32_IF_MSG(ERROR_TIMEOUT, !m_containerdReadyEvent.wait(10 * 1000), "Timed out waiting for containerd to start");
// Wait for dockerd to be ready before starting the event tracker.
THROW_WIN32_IF_MSG(
ERROR_TIMEOUT, !m_containerdReadyEvent.wait(Settings.BootTimeoutMs), "Timed out waiting for dockerd to start");
auto [_, __, channel] = m_virtualMachine->Fork(WSLA_FORK::Thread);
m_dockerClient.emplace(std::move(channel), m_virtualMachine->ExitingEvent(), m_virtualMachine->VmId(), 10 * 1000);
// Start the event tracker.
m_eventTracker.emplace(m_dockerClient.value());
m_eventTracker.emplace(m_dockerClient.value(), m_id, m_ioRelay);
// Recover any existing containers from storage.
RecoverExistingContainers();
@ -164,7 +155,7 @@ WSLASession::~WSLASession()
{
WSL_LOG("SessionTerminated", TraceLoggingValue(m_displayName.c_str(), "DisplayName"));
Terminate();
LOG_IF_FAILED(Terminate());
}
void WSLASession::ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings, PSID UserSid)
@ -277,7 +268,15 @@ void WSLASession::CopyDisplayName(_Out_writes_z_(bufferLength) PWSTR buffer, siz
wcscpy_s(buffer, bufferLength, m_displayName.c_str());
}
void WSLASession::OnContainerdLog(const gsl::span<char>& buffer)
void WSLASession::OnDockerdExited()
{
if (!m_sessionTerminatingEvent.is_signaled())
{
WSL_LOG("UnexpectedDockerdExit", TraceLoggingValue(m_displayName.c_str(), "Name"));
}
}
void WSLASession::OnDockerdLog(const gsl::span<char>& buffer)
try
{
if (buffer.empty())
@ -300,40 +299,30 @@ try
}
CATCH_LOG();
void WSLASession::MonitorContainerd(ServiceRunningProcess&& process)
try
void WSLASession::StartDockerd()
{
windows::common::relay::MultiHandleWait io;
std::vector<std::string> args{{"/usr/bin/dockerd"}};
if (WI_IsFlagSet(m_featureFlags, WslaFeatureFlagsDebug))
{
args.emplace_back("--debug");
}
ServiceProcessLauncher launcher{"/usr/bin/dockerd", args, {{"PATH=/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin"}}};
m_dockerdProcess = launcher.Launch(*m_virtualMachine);
// Read stdout & stderr.
io.AddHandle(std::make_unique<windows::common::relay::LineBasedReadHandle>(
process.GetStdHandle(1), [&](const auto& data) { OnContainerdLog(data); }, false));
m_ioRelay.AddHandle(std::make_unique<windows::common::relay::LineBasedReadHandle>(
m_dockerdProcess->GetStdHandle(1), [&](const auto& data) { OnDockerdLog(data); }, false));
io.AddHandle(std::make_unique<windows::common::relay::LineBasedReadHandle>(
process.GetStdHandle(2), [&](const auto& data) { OnContainerdLog(data); }, false));
m_ioRelay.AddHandle(std::make_unique<windows::common::relay::LineBasedReadHandle>(
m_dockerdProcess->GetStdHandle(2), [&](const auto& data) { OnDockerdLog(data); }, false));
// Exit if either the VM terminates or containerd exits.
io.AddHandle(std::make_unique<windows::common::relay::EventHandle>(process.GetExitEvent()), MultiHandleWait::CancelOnCompleted);
io.AddHandle(std::make_unique<windows::common::relay::EventHandle>(m_sessionTerminatingEvent.get()), MultiHandleWait::CancelOnCompleted);
io.Run({});
if (!m_sessionTerminatingEvent.is_signaled())
{
// If containerd exited before the VM starts terminating, then it exited unexpectedly.
WSL_LOG("UnexpectedContainerdExit", TraceLoggingValue(m_displayName.c_str(), "SessionDisplayName"));
}
else
{
// Otherwise, the session is shutting down; terminate containerd before exiting.
LOG_IF_FAILED(process.Get().Signal(15)); // SIGTERM
auto code = process.Wait(30 * 1000); // TODO: Configurable timeout.
WSL_LOG("DockerdExit", TraceLoggingValue(code, "code"));
}
// Monitor dockerd's exist so we can detect abnormal exits.
m_ioRelay.AddHandle(std::make_unique<windows::common::relay::EventHandle>(
m_dockerdProcess->GetExitEvent(), std::bind(&WSLASession::OnDockerdExited, this)));
}
CATCH_LOG();
HRESULT WSLASession::PullImage(
LPCSTR ImageUri,
@ -781,7 +770,8 @@ try
*m_virtualMachine,
std::bind(&WSLASession::OnContainerDeleted, this, std::placeholders::_1),
m_eventTracker.value(),
m_dockerClient.value()));
m_dockerClient.value(),
m_ioRelay));
THROW_IF_FAILED(it->ComWrapper().QueryInterface(__uuidof(IWSLAContainer), (void**)Container));
@ -938,26 +928,44 @@ try
std::lock_guard lock{m_lock};
// Stop the event tracker
if (m_eventTracker.has_value())
{
m_eventTracker->Stop();
}
// This will delete all containers. Needs to be done before the VM is terminated.
m_containers.clear();
// Stop the IO relay.
// This stops:
// - container state monitoring.
// - container init process relays
// - execs relays
// - container logs relays
m_ioRelay.Stop();
m_eventTracker.reset();
m_dockerClient.reset();
// N.B. The containerd thread can only run if the VM is running.
if (m_containerdThread.joinable())
// Stop dockerd.
// N.B. dockerd wait a couple seconds if there are any outstanding HTTP request sockets opened.
if (m_dockerdProcess.has_value())
{
m_containerdThread.join();
LOG_IF_FAILED(m_dockerdProcess->Get().Signal(WSLASignalSIGTERM));
int exitCode = -1;
try
{
exitCode = m_dockerdProcess->Wait(30 * 1000);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
m_dockerdProcess->Get().Signal(WSLASignalSIGKILL);
exitCode = m_dockerdProcess->Wait(10 * 1000);
}
WSL_LOG("DockerdExit", TraceLoggingValue(exitCode, "code"));
}
if (m_virtualMachine)
{
// N.B. containerd has exited by this point, so unmounting the VHD is safe since no container can be running.
// N.B. dockerd has exited by this point, so unmounting the VHD is safe since no container can be running.
try
{
m_virtualMachine->Unmount(c_containerdStorage);
@ -1053,7 +1061,8 @@ void WSLASession::RecoverExistingContainers()
*m_virtualMachine,
std::bind(&WSLASession::OnContainerDeleted, this, std::placeholders::_1),
m_eventTracker.value(),
m_dockerClient.value());
m_dockerClient.value(),
m_ioRelay);
m_containers.emplace_back(std::move(container));
}

View File

@ -19,6 +19,7 @@ Abstract:
#include "WSLAContainer.h"
#include "ContainerEventTracker.h"
#include "DockerHTTPClient.h"
#include "IORelay.h"
namespace wsl::windows::service::wsla {
@ -107,8 +108,9 @@ private:
void ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings, PSID UserSid);
void Ext4Format(const std::string& Device);
void OnContainerDeleted(const WSLAContainerImpl* Container);
void OnContainerdLog(const gsl::span<char>& Data);
void MonitorContainerd(ServiceRunningProcess&& process);
void OnDockerdLog(const gsl::span<char>& Data);
void OnDockerdExited();
void StartDockerd();
void ImportImageImpl(DockerHTTPClient::HTTPRequestContext& Request, ULONG InputHandle);
void RecoverExistingContainers();
@ -119,7 +121,6 @@ private:
std::optional<WSLAVirtualMachine> m_virtualMachine;
std::optional<ContainerEventTracker> m_eventTracker;
wil::unique_event m_containerdReadyEvent{wil::EventOptions::ManualReset};
std::thread m_containerdThread;
std::wstring m_displayName;
std::filesystem::path m_storageVhdPath;
std::vector<std::unique_ptr<WSLAContainerImpl>> m_containers;
@ -128,6 +129,9 @@ private:
bool m_elevatedToken{};
DWORD m_creatorPid{};
std::recursive_mutex m_lock;
IORelay m_ioRelay;
std::optional<ServiceRunningProcess> m_dockerdProcess;
WSLAFeatureFlags m_featureFlags{};
};
} // namespace wsl::windows::service::wsla

View File

@ -561,11 +561,13 @@ void WSLAVirtualMachine::ConfigureNetworking()
wsl::core::NatNetworking::CreateNetwork(config),
std::move(gnsChannel),
config,
dnsChannelFd != -1 ? wil::unique_socket{(SOCKET)process->GetStdHandle(dnsChannelFd).release()} : wil::unique_socket{});
dnsChannelFd != -1 ? wil::unique_socket{(SOCKET)process->GetStdHandle(dnsChannelFd).release()} : wil::unique_socket{},
nullptr);
}
else
{
m_networkEngine = std::make_unique<wsl::core::VirtioNetworking>(std::move(gnsChannel), false, m_guestDeviceManager, m_userToken);
m_networkEngine =
std::make_unique<wsl::core::VirtioNetworking>(std::move(gnsChannel), false, nullptr, m_guestDeviceManager, m_userToken);
}
m_networkEngine->Initialize();

View File

@ -195,6 +195,15 @@ typedef enum _WSLAContainerFlags
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLAContainerFlags);")
typedef enum _WSLAContainerStartFlags
{
WSLAContainerStartFlagsNone = 0,
WSLAContainerStartFlagsAttach = 1, // Attach stdio handles on start.
} WSLAContainerStartFlags;
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLAContainerStartFlags);")
struct WSLA_CONTAINER_OPTIONS
{
LPCSTR Image;
@ -279,8 +288,11 @@ typedef enum _WSLAFeatureFlags
WslaFeatureFlagsGPU = 4,
WslaFeatureFlagsVirtioFs = 8,
WslaFeatureFlagsPmemVhds = 16,
WslaFeatureFlagsDebug = 32,
} WSLAFeatureFlags;
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLAFeatureFlags);")
struct WSLA_SESSION_SETTINGS {
LPCWSTR DisplayName;
LPCWSTR StoragePath;
@ -290,7 +302,7 @@ struct WSLA_SESSION_SETTINGS {
ULONG BootTimeoutMs;
WSLANetworkingMode NetworkingMode;
[unique] ITerminationCallback* TerminationCallback;
ULONG FeatureFlags;
WSLAFeatureFlags FeatureFlags;
ULONG DmesgOutput;
// Below options are used for debugging purposes only.
@ -316,7 +328,7 @@ interface IWSLAContainer : IUnknown
{
HRESULT Attach([out] ULONG* StdIn, [out] ULONG* StdOut, [out] ULONG* StdErr);
HRESULT Stop([in] WSLASignal Signal, [in] LONGLONG TimeoutSeconds);
HRESULT Start();
HRESULT Start([in] WSLAContainerStartFlags Flags);
HRESULT Delete(); // TODO: Look into lifetime logic.
HRESULT GetState([out] enum WSLA_CONTAINER_STATE* State);
HRESULT GetInitProcess([out] IWSLAProcess** Process);

View File

@ -294,7 +294,7 @@ Return Value:
snprintf(
Plan9Options,
sizeof(Plan9Options),
"aname=drvfs;path=%s%s;symlinkroot=/mnt/,cache=5,access=client,msize=65536,trans=fd,rfd=4,wfd=4",
"aname=drvfs;path=%s%s;symlinkroot=/mnt/,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*",
Plan9Source,
Temp);
}

View File

@ -28,6 +28,7 @@ Abstract:
#define PATH_MAX (4096)
void MountEscapeString(const char* Source, char* Dest, size_t Length);
int MountCheckFsOptionsPattern(const char* Pattern, const char* Actual);
int MountCheckIsMount(
const char* Path,
@ -127,7 +128,14 @@ Return Value:
LxtCheckStringEqual(ExpectedMountOptions, mnt_fs_get_vfs_options(FileSystem));
if (ExpectedFsOptions != NULL)
{
LxtCheckStringEqual(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem));
if (strchr(ExpectedFsOptions, '*') != NULL)
{
LxtCheckResult(MountCheckFsOptionsPattern(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem)));
}
else
{
LxtCheckStringEqual(ExpectedFsOptions, mnt_fs_get_fs_options(FileSystem));
}
}
LxtCheckEqual(Stat.st_dev, mnt_fs_get_devno(FileSystem), "%lu");
@ -235,6 +243,109 @@ ErrorExit:
return Result;
}
int MountCheckFsOptionsPattern(const char* Pattern, const char* Actual)
/*++
Description:
This routine checks if mount options match a pattern with wildcards.
The '*' wildcard matches any sequence of characters.
Arguments:
Pattern - Supplies the expected pattern (e.g., "rfd=*,wfd=*").
Actual - Supplies the actual mount options string.
Return Value:
Returns 0 on success, -1 on failure.
--*/
{
const char* ActualPtr;
const char* MatchPosition;
const char* PatternPtr;
int Result;
const char* StarPosition;
PatternPtr = Pattern;
ActualPtr = Actual;
StarPosition = NULL;
MatchPosition = NULL;
Result = LXT_RESULT_FAILURE;
while (*ActualPtr != '\0')
{
if (*PatternPtr == '*')
{
//
// Remember position of * and where we are in actual string.
//
StarPosition = PatternPtr++;
MatchPosition = ActualPtr;
}
else if (*PatternPtr == *ActualPtr)
{
//
// Characters match, advance both.
//
PatternPtr++;
ActualPtr++;
}
else if (StarPosition != NULL)
{
//
// Mismatch but we have a *, backtrack and try matching one more character.
//
PatternPtr = StarPosition + 1;
ActualPtr = ++MatchPosition;
}
else
{
//
// Mismatch and no * to backtrack to.
//
goto ErrorExit;
}
}
//
// Consume any trailing *'s in pattern.
//
while (*PatternPtr == '*')
{
PatternPtr++;
}
//
// Both should be at end of string.
//
if (*PatternPtr != '\0')
{
goto ErrorExit;
}
Result = LXT_RESULT_SUCCESS;
ErrorExit:
if (!LXT_SUCCESS(Result))
{
LxtLogError("Mount options mismatch: expected '%s', got '%s'", Pattern, Actual);
}
return Result;
}
void MountEscapeString(const char* Source, char* Dest, size_t Length)
/*++

View File

@ -1706,11 +1706,7 @@ Return Value:
if (LxsstuVmMode())
{
std::wstring Command = L"/bin/cp /data/test/log/";
Command += LogFileToken;
Command += L" $(wslpath '";
Command += LinuxLogPath;
Command += L"')";
std::wstring Command = std::format(L"/bin/cp /data/test/log/{} $(wslpath '{}')", LogFileToken, LinuxLogPath);
VERIFY_NO_THROW(LxsstuRunTest(Command.c_str()));
}

View File

@ -226,12 +226,6 @@ public:
{
SKIP_TEST_ARM64();
if (Mode == DrvFsMode::VirtioFs)
{
LogSkipped("VirtioFS currently only supports mounting full drives");
return;
}
constexpr auto MountPoint = "C:\\lxss_fat";
constexpr auto VhdPath = "C:\\lxss_fat.vhdx";
auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
@ -352,12 +346,6 @@ public:
SKIP_TEST_ARM64();
WSL_TEST_VERSION_REQUIRED(wsl::windows::common::helpers::WindowsBuildNumbers::Germanium);
if (Mode == DrvFsMode::VirtioFs)
{
LogSkipped("VirtioFS currently only supports mounting full drives");
return;
}
constexpr auto MountPoint = "C:\\lxss_refs";
constexpr auto VhdPath = "C:\\lxss_refs.vhdx";
auto Cleanup = wil::scope_exit([MountPoint, VhdPath] { DeleteVolume(MountPoint, VhdPath); });
@ -367,6 +355,68 @@ public:
LxsstuRunTest((L"bash -c '" + SkipUnstableTestEnvVar + L" /data/test/wsl_unit_tests drvfs -m 6'").c_str(), L"drvfs6"));
}
void WslPath(DrvFsMode Mode)
{
VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests wslpath", L"wslpath"));
auto testWslPath = [](const std::wstring& testDir) {
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testDir); });
std::filesystem::create_directory(testDir);
auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -aw '{}'", testDir));
VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -wa '{}'", testDir));
VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath '{}'", testDir));
VERIFY_ARE_EQUAL(std::format(L"{}\n", testDir), out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -a '{}'", testDir));
VERIFY_IS_TRUE(out.find(L"/mnt/") == 0);
};
testWslPath(L"wslpath-test-dir");
testWslPath(L"wslpath-测试目录-テスト");
}
void DrvFsMountUnicodePath(DrvFsMode Mode)
{
WSL2_TEST_ONLY();
// Create a Windows directory with unicode characters
constexpr auto unicodeDir = L"C:\\drvfs-测试-テスト";
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(unicodeDir); });
std::filesystem::create_directory(unicodeDir);
// Create a test file inside the directory
const auto testFilePath = std::filesystem::path(unicodeDir) / L"test-file.txt";
{
std::ofstream testFile(testFilePath);
testFile << "hello from unicode path";
}
// Mount the unicode directory using mount -t drvfs
constexpr auto mountPoint = L"/tmp/unicode-mount-test";
auto unmountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
LxsstuLaunchWsl(std::format(L"-u root umount '{}'", mountPoint).c_str());
LxsstuLaunchWsl(std::format(L"-u root rmdir '{}'", mountPoint).c_str());
});
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mkdir -p '{}'", mountPoint).c_str()), 0);
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root mount -t drvfs '{}' '{}'", unicodeDir, mountPoint).c_str()), 0);
// Verify we can read the test file through the mount
auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}/test-file.txt'", mountPoint));
VERIFY_ARE_EQUAL(L"hello from unicode path", out);
// Verify we can list the directory
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"ls '{}'", mountPoint));
VERIFY_IS_TRUE(out.find(L"test-file.txt") != std::wstring::npos);
}
// DrvFsTests Private Methods
private:
static VOID CreateDrvFsTestFiles(bool Metadata)
@ -935,7 +985,11 @@ private:
const auto lines = LxssSplitString(output.Stdout, L"\n");
VERIFY_ARE_EQUAL(lines.size(), 1);
VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0);
if (!expectedType.empty())
{
VERIFY_IS_TRUE(output.Stdout.find(expectedType) == 0);
}
};
std::wstring elevatedType;
@ -951,8 +1005,7 @@ private:
nonElevatedType = L"drvfs";
break;
case DrvFsMode::VirtioFs:
elevatedType = L"drvfsaC";
nonElevatedType = L"drvfsC";
// VirtioFs uses GUIDs as the tag so the value is not predictable.
break;
default:
@ -1149,6 +1202,12 @@ class WSL1 : public DrvFsTests
WSL1_TEST_ONLY();
DrvFsTests::XattrDrvFs(DrvFsMode::WSL1);
}
TEST_METHOD(WslPath)
{
WSL1_TEST_ONLY();
DrvFsTests::WslPath(DrvFsMode::WSL1);
}
};
#define WSL2_DRVFS_TEST_CLASS(_mode) \
@ -1266,6 +1325,18 @@ class WSL1 : public DrvFsTests
WSL2_TEST_ONLY(); \
DrvFsTests::DrvFsReFs(DrvFsMode::##_mode##); \
} \
\
TEST_METHOD(WslPath) \
{ \
WSL2_TEST_ONLY(); \
DrvFsTests::WslPath(DrvFsMode::##_mode##); \
} \
\
TEST_METHOD(DrvFsMountUnicodePath) \
{ \
WSL2_TEST_ONLY(); \
DrvFsTests::DrvFsMountUnicodePath(DrvFsMode::##_mode##); \
} \
}
WSL2_DRVFS_TEST_CLASS(Plan9);

View File

@ -1037,7 +1037,6 @@ class NetworkTests
wsl::shared::hns::DNS dns;
dns.ServerList = L"1.1.1.1,1.1.1.2";
dns.Domain = L"microsoft.com";
dns.Search = L"foo.microsoft.com,bar.microsoft.com";
dns.Options = LX_INIT_RESOLVCONF_FULL_HEADER;
RunGns(dns, ModifyRequestType::Update, GuestEndpointResourceType::DNS);
@ -1047,7 +1046,6 @@ class NetworkTests
const std::wstring expected = std::wstring(LX_INIT_RESOLVCONF_FULL_HEADER) +
L"nameserver 1.1.1.1\n"
L"nameserver 1.1.1.2\n"
L"domain microsoft.com\n"
L"search foo.microsoft.com bar.microsoft.com\n";
VERIFY_ARE_EQUAL(expected, out.c_str());
}
@ -4612,6 +4610,9 @@ class VirtioProxyTests
const std::wregex pattern(L"(.|\\n)*nameserver [0-9. ]+(.|\\n)*");
VERIFY_IS_TRUE(std::regex_match(out, pattern));
// Verify that /etc/resolv.conf contains a 'search' line with DNS suffixes
VERIFY_IS_TRUE(out.find(L"search ") != std::wstring::npos);
}
TEST_METHOD(GuestPortIsReleased)

View File

@ -878,11 +878,6 @@ class UnitTests
}
}
TEST_METHOD(WslPath)
{
VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests wslpath", L"wslpath"));
}
TEST_METHOD(FsTab)
{
//
@ -5958,25 +5953,6 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
VERIFY_IS_TRUE(a);
VERIFY_ARE_EQUAL(pos, L"-");
}
{
constexpr auto testDir = "wslpath-test-dir";
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { std::filesystem::remove_all(testDir); });
std::filesystem::create_directory(testDir);
auto [out, err] = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -aw {}", testDir));
VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -wa {}", testDir));
VERIFY_ARE_EQUAL((std::filesystem::canonical(std::filesystem::current_path()) / testDir).wstring() + L"\n", out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath {}", testDir));
VERIFY_ARE_EQUAL(std::format(L"{}\n", testDir), out);
std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput(std::format(L"wslpath -a {}", testDir));
VERIFY_IS_TRUE(out.find(L"/mnt/") == 0);
}
}
TEST_METHOD(CaseSensitivity)

View File

@ -29,8 +29,6 @@ using wsl::windows::common::relay::OverlappedIOHandle;
using wsl::windows::common::relay::WriteHandle;
using wsl::windows::common::wslutil::WSLAErrorDetails;
DEFINE_ENUM_FLAG_OPERATORS(WSLAFeatureFlags);
extern std::wstring g_testDataPath;
extern bool g_fastTestRun;
@ -1432,7 +1430,7 @@ class WSLATests
// Validate that stdin behaves correctly if closed without any input.
{
WSLAContainerLauncher launcher("debian:latest", "test-stdin", {"/bin/cat"});
WSLAContainerLauncher launcher("debian:latest", "test-stdin", {"/bin/cat"}, {}, {}, WSLAProcessFlagsStdin);
auto container = launcher.Launch(*m_defaultSession);
auto process = container.GetInitProcess();
process.GetStdHandle(0); // Close stdin;
@ -1811,10 +1809,10 @@ class WSLATests
VERIFY_SUCCEEDED(result);
VERIFY_ARE_EQUAL(container->State(), WslaContainerStateCreated);
VERIFY_SUCCEEDED(container->Get().Start());
VERIFY_SUCCEEDED(container->Get().Start(WSLAContainerStartFlagsNone));
// Verify that Start() can't be called again on a running container.
VERIFY_ARE_EQUAL(container->Get().Start(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
VERIFY_ARE_EQUAL(container->Get().Start(WSLAContainerStartFlagsNone), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
VERIFY_ARE_EQUAL(container->State(), WslaContainerStateRunning);
@ -2926,7 +2924,7 @@ class WSLATests
HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
// Start the container.
VERIFY_SUCCEEDED(container->Get().Start());
VERIFY_SUCCEEDED(container->Get().Start(WSLAContainerStartFlagsAttach));
// Get its original std handles.
auto process = container->GetInitProcess();
@ -3035,5 +3033,33 @@ class WSLATests
originalReader.ExpectClosed();
attachedReader.ExpectClosed();
}
// Validate that containers can be started in detached mode and attached to later.
{
WSLAContainerLauncher launcher("debian:latest", "attach-test-4", {"/bin/cat"}, {}, {}, WSLAProcessFlagsStdin);
auto container = launcher.Launch(*m_defaultSession, WSLAContainerStartFlagsNone);
auto initProcess = container.GetInitProcess();
ULONG dummy{};
VERIFY_ARE_EQUAL(initProcess.Get().GetStdHandle(WSLAFDStdin, &dummy), HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
VERIFY_ARE_EQUAL(initProcess.Get().GetStdHandle(WSLAFDStdout, &dummy), HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
VERIFY_ARE_EQUAL(initProcess.Get().GetStdHandle(WSLAFDStderr, &dummy), HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
// Verify that the container can be attached to.
wil::unique_handle attachedStdin;
wil::unique_handle attachedStdout;
wil::unique_handle attachedStderr;
VERIFY_SUCCEEDED(container.Get().Attach((ULONG*)&attachedStdin, (ULONG*)&attachedStdout, (ULONG*)&attachedStderr));
PartialHandleRead attachedReader(attachedStdout.get());
// Write content on the attached stdin.
VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(attachedStdin.get(), "OK\n", 3, nullptr, nullptr));
attachedStdin.reset();
attachedReader.Expect("OK\n");
attachedReader.ExpectClosed();
VERIFY_ARE_EQUAL(initProcess.Wait(), 0);
}
}
};