mirror of
https://github.com/microsoft/WSL.git
synced 2026-02-04 02:06:49 -06:00
Merge branch 'wsl-for-apps' into user/ptrivedi/saveimage-exportcont
This commit is contained in:
commit
f00e95b790
@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@ -292,10 +292,12 @@ a wsl.exe --install <Distro>.</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
@ -298,10 +298,12 @@ und "wsl.exe --install <Distro>".</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
@ -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
@ -292,9 +292,11 @@ e "wsl.exe --install <Distro>" 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>
|
||||
|
||||
@ -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 <DistributionName> --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>Dağı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">
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {};
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
/*++
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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.
|
||||
//
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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{};
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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{};
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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()>;
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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};
|
||||
};
|
||||
|
||||
@ -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()}};
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
96
src/windows/wslaservice/exe/IORelay.cpp
Normal file
96
src/windows/wslaservice/exe/IORelay.cpp
Normal 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();
|
||||
45
src/windows/wslaservice/exe/IORelay.h
Normal file
45
src/windows/wslaservice/exe/IORelay.h
Normal 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
|
||||
@ -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();
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
/*++
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user