From 1c0a783932c2f491685595dcee6d26f8d93d9752 Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Fri, 24 Apr 2026 15:36:54 -0700 Subject: [PATCH 1/2] merge master -> feature/wsl-for-apps --- .pipelines/nuget-stage.yml | 4 +- .pipelines/package-stage.yml | 2 +- cgmanifest.json | 16 +- diagnostics/collect-wsl-logs.ps1 | 4 + distributions/DistributionInfo.json | 12 +- localization/strings/cs-CZ/Resources.resw | 44 ++ localization/strings/da-DK/Resources.resw | 44 ++ localization/strings/de-DE/Resources.resw | 44 ++ localization/strings/en-GB/Resources.resw | 44 ++ localization/strings/en-US/Resources.resw | 44 ++ localization/strings/es-ES/Resources.resw | 44 ++ localization/strings/fi-FI/Resources.resw | 44 ++ localization/strings/fr-FR/Resources.resw | 44 ++ localization/strings/hu-HU/Resources.resw | 44 ++ localization/strings/it-IT/Resources.resw | 44 ++ localization/strings/ja-JP/Resources.resw | 44 ++ localization/strings/ko-KR/Resources.resw | 44 ++ localization/strings/nb-NO/Resources.resw | 44 ++ localization/strings/nl-NL/Resources.resw | 44 ++ localization/strings/pl-PL/Resources.resw | 44 ++ localization/strings/pt-BR/Resources.resw | 44 ++ localization/strings/pt-PT/Resources.resw | 44 ++ localization/strings/ru-RU/Resources.resw | 44 ++ localization/strings/sv-SE/Resources.resw | 44 ++ localization/strings/tr-TR/Resources.resw | 44 ++ localization/strings/zh-CN/Resources.resw | 44 ++ localization/strings/zh-TW/Resources.resw | 44 ++ src/linux/inc/lxwil.h | 4 +- src/linux/init/DnsServer.cpp | 1 - src/linux/init/GnsEngine.cpp | 14 +- src/linux/init/GnsEngine.h | 8 +- src/linux/init/GnsPortTracker.cpp | 95 +-- src/linux/init/GnsPortTracker.h | 15 +- src/linux/init/SecCompDispatcher.cpp | 10 +- src/linux/init/binfmt.cpp | 3 +- src/linux/init/config.cpp | 33 +- src/linux/init/config.h | 7 +- src/linux/init/drvfs.cpp | 5 +- src/linux/init/init.cpp | 119 ++-- src/linux/init/localhost.cpp | 6 +- src/linux/init/main.cpp | 29 +- src/linux/init/plan9.cpp | 7 +- src/linux/init/util.cpp | 29 +- src/linux/init/util.h | 5 +- src/linux/init/wslinfo.cpp | 2 +- src/linux/mountutil/mountutil.c | 1 + src/linux/netlinkutil/Interface.cpp | 6 +- src/linux/netlinkutil/NetlinkMessage.hxx | 2 +- src/linux/plan9/p9file.cpp | 2 +- src/linux/plan9/p9file.h | 2 +- src/linux/plan9/p9io.cpp | 14 +- src/linux/plan9/p9readdir.cpp | 2 +- src/linux/plan9/p9util.cpp | 4 +- src/shared/configfile/configfile.cpp | 4 +- src/shared/inc/SocketChannel.h | 369 +++++++++-- src/shared/inc/lxinitshared.h | 34 +- src/shared/inc/message.h | 8 +- src/shared/inc/prettyprintshared.h | 26 +- src/shared/inc/socketshared.h | 14 +- src/shared/inc/stringshared.h | 9 +- src/windows/common/GnsPortTrackerChannel.cpp | 8 +- .../common/HandleConsoleProgressBar.cpp | 2 +- .../common/WslCoreNetworkingSupport.cpp | 4 +- src/windows/common/WslInstall.cpp | 1 + src/windows/common/filesystem.cpp | 4 +- src/windows/common/helpers.cpp | 4 +- src/windows/common/relay.cpp | 2 +- src/windows/common/socket.cpp | 2 +- src/windows/common/wslutil.cpp | 53 +- src/windows/service/exe/BridgedNetworking.cpp | 2 +- .../service/exe/DistributionRegistration.cpp | 1 - src/windows/service/exe/LxssCreateProcess.h | 6 +- src/windows/service/exe/LxssUserSession.cpp | 37 +- src/windows/service/exe/WslCoreInstance.cpp | 13 +- src/windows/service/exe/WslCoreVm.cpp | 68 +- src/windows/wslinstaller/exe/WslInstaller.cpp | 31 +- src/windows/wslrelay/localhost.cpp | 7 +- src/windows/wslsettings/CMakeLists.txt | 1 + .../Contracts/Services/IWslConfigService.cs | 67 ++ .../wslsettings/Services/WslConfigService.cs | 620 ++++++++++++------ .../Settings/OptionalFeaturesPage.xaml.cs | 2 +- .../Views/Settings/SettingsApplyHelper.cs | 221 +++++++ .../wslsettings/Views/Settings/ShellPage.xaml | 7 + .../Views/Settings/ShellPage.xaml.cs | 28 +- .../wslsettings/directory.build.targets.in | 4 + test/windows/NetworkTests.cpp | 53 ++ 86 files changed, 2571 insertions(+), 572 deletions(-) create mode 100644 src/windows/wslsettings/Views/Settings/SettingsApplyHelper.cs diff --git a/.pipelines/nuget-stage.yml b/.pipelines/nuget-stage.yml index f38c29de4..2700ed93e 100644 --- a/.pipelines/nuget-stage.yml +++ b/.pipelines/nuget-stage.yml @@ -35,7 +35,7 @@ stages: displayName: Download nuget artifacts inputs: artifact: "drop_wsl_package" - path: drop + path: $(Build.SourcesDirectory)\drop # Note: this task might fail if there's been no commits between two nightly pipelines, which is fine. - ${{ each package in parameters.nugetPackages }}: @@ -43,7 +43,7 @@ stages: displayName: Push nuget/${{ package }}.*.nupkg inputs: command: 'push' - packagesToPush: drop/bin/nuget/${{ package }}.*.nupkg + packagesToPush: $(Build.SourcesDirectory)\drop\nuget\${{ package }}.$(WSL_NUGET_PACKAGE_VERSION).nupkg nuGetFeedType: 'internal' publishVstsFeed: wsl allowPackageConflicts: ${{ parameters.isNightly }} \ No newline at end of file diff --git a/.pipelines/package-stage.yml b/.pipelines/package-stage.yml index d3f2dc0a2..c421606d3 100644 --- a/.pipelines/package-stage.yml +++ b/.pipelines/package-stage.yml @@ -82,7 +82,7 @@ stages: New-Item -ItemType Directory -Path $dest -Force Copy-Item "$(Pipeline.Workspace)\drop_$($arch.platform)\installer\installer.$($arch.platform).msix" "$dest\installer.msix" Copy-Item "$(Pipeline.Workspace)\drop_$($arch.platform)\sdk\$($arch.platform)\wslcsdk.lib" "$dest\wslcsdk.lib" - Copy-Item "$(Pipeline.Workspace)\drop_$($arch.platform)\sdk\$($arch.platform)\wslcsdk.dll" "$dest\wslcsdk.dll" + Copy-Item "$(Pipeline.Workspace)\drop_$($arch.platform)\sdk\$($arch.platform)\wslcsdk.dll" "$dest\wslcsdk.dll" } # Copy MSIs to the output bundle directory diff --git a/cgmanifest.json b/cgmanifest.json index d49f7275e..f81e699f3 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -59,14 +59,14 @@ "version": "0.9.0", "downloadUrl": "https://github.com/jbeder/yaml-cpp/releases/download/yaml-cpp-0.9.0/yaml-cpp-yaml-cpp-0.9.0.tar.gz", "hash": "sha256:298593d9c440fd9034b8b193d96318b76d49bc97c6ceadb7b0836edf0b6d7539" - - } - } - }, - { - "component": { - "type": "other", - "other": { + + } + } + }, + { + "component": { + "type": "other", + "other": { "name": "GSL", "version": "4.0.0", "downloadUrl": "https://github.com/microsoft/GSL/archive/refs/tags/v4.0.0.tar.gz", diff --git a/diagnostics/collect-wsl-logs.ps1 b/diagnostics/collect-wsl-logs.ps1 index 31323cf4e..02fdff72b 100644 --- a/diagnostics/collect-wsl-logs.ps1 +++ b/diagnostics/collect-wsl-logs.ps1 @@ -177,8 +177,12 @@ if (Test-Path $wslconfig) Copy-Item $wslconfig $folder | Out-Null } +# Collect high-level WSL install log (written by WriteInstallLog() in install.cpp) Copy-Item "C:\Windows\temp\wsl-install-log.txt" $folder -ErrorAction ignore +# Collect MSI verbose install log (preserved on failure by wsl --update or WslInstaller service). +Copy-Item "$env:TEMP\wsl-install-logs.txt" $folder -ErrorAction ignore + get-appxpackage MicrosoftCorporationII.WindowsSubsystemforLinux -ErrorAction Ignore > $folder/appxpackage.txt get-acl "C:\ProgramData\Microsoft\Windows\WindowsApps" -ErrorAction Ignore | Format-List > $folder/acl.txt Get-WindowsOptionalFeature -Online > $folder/optional-components.txt diff --git a/distributions/DistributionInfo.json b/distributions/DistributionInfo.json index 5999fa634..28d13b737 100644 --- a/distributions/DistributionInfo.json +++ b/distributions/DistributionInfo.json @@ -60,12 +60,12 @@ "FriendlyName": "openSUSE Tumbleweed", "Default": true, "Amd64Url": { - "Url": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260106.0/openSUSE-Tumbleweed-20260103.x86_64-1.224-Build1.224.wsl", - "Sha256": "0x394be699da2821b331355f3541e237aa3aa00bc4068f33283d68303d8336d484" + "Url": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260423.0/openSUSE-Tumbleweed-20260422.x86_64-2.97-Build2.97.wsl", + "Sha256": "0xc0dcf707276626e7a6227404c6a1e351dfdb9d39f2538d2b8a78a2c2e41f5912" }, "Arm64Url": { - "Url": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260106.0/openSUSE-Tumbleweed-20260103.aarch64-2.195-Build2.195.wsl", - "Sha256": "0xbcbb88e957091c425ecb42f3076b8882b5976fd94885e453afaee40de3b79470" + "Url": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260423.0/openSUSE-Tumbleweed-20260422.aarch64-3.82-Build3.82.wsl", + "Sha256": "0x70d02e702b7788c494ad785bc3987ac1361f263af99d2c8180aac269cf0a9747" } }, { @@ -325,8 +325,8 @@ "StoreAppId": "9MSSK2ZXXN11", "Amd64": true, "Arm64": true, - "Amd64PackageUrl": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260106.0/openSUSE-Tumbleweed-20260103-WSL.x86_64-26003.9.1368.0-Build9.1368.appx", - "Arm64PackageUrl": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260106.0/openSUSE-Tumbleweed-20260103-WSL.aarch64-26003.9.741.0-Build9.741.appx", + "Amd64PackageUrl": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260423.0/openSUSE-Tumbleweed-20260422-WSL.x86_64-26112.10.203.0-Build10.203.appx", + "Arm64PackageUrl": "https://github.com/openSUSE/WSL-instarball/releases/download/v20260423.0/openSUSE-Tumbleweed-20260422-WSL.aarch64-26112.10.154.0-Build10.154.appx", "PackageFamilyName": "46932SUSE.openSUSETumbleweed_022rs5jcyhyac" } ] diff --git a/localization/strings/cs-CZ/Resources.resw b/localization/strings/cs-CZ/Resources.resw index 642245948..080b20409 100644 --- a/localization/strings/cs-CZ/Resources.resw +++ b/localization/strings/cs-CZ/Resources.resw @@ -744,6 +744,11 @@ Informace najdete na https://aka.ms/wslinstall Spuštění ladicího prostředí vyžaduje spuštění wsl.exe jako správce. + + Windows Admin Protection je povolená a vaše distribuce se můžou zaregistrovat pod jiným účtem. +Další informace o Admin Protection najdete na https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Proces instalace pro distribuci '{}' selhal s ukončovacím kódem: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Návrat k sítím NAT. Verze Systému Windows {}. {} nemá požadované funkce. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Protokol IPv6 je na hostiteli zakázán nepodporovaným způsobem (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Brána firewall technologie Hyper-V není podporována. @@ -1877,6 +1886,41 @@ K dalším možnostem VS Code Remote můžete přistupovat také prostřednictv Počet milisekund, po které je virtuální počítač nečinný, než se vypne. + + Použít změny + + + Je potřeba vypnout WSL + + + Změny nastavení WSL vyžadují k dokončení vypnutí a restartování WSL. Po vypnutí se ukončí všechny aktivní relace WSL. + + + Vypnout WSL teď + + + Později + + + Nepovedlo se použít změny + + + Zavřít + + + Nepovedlo se uložit čekající změny nastavení. Kód chyby: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Nastavení se uložila, ale vypnutí WSL se nezdařilo: {0} + {Locked="{0}"}Format placeholder for exception message + + + Zapnuto + + + Vypnuto + Vytvářejte, spouštějte, laďte a profilujte své aplikace běžící na WSL z Visual Studia diff --git a/localization/strings/da-DK/Resources.resw b/localization/strings/da-DK/Resources.resw index f8727c206..93d4c41a5 100644 --- a/localization/strings/da-DK/Resources.resw +++ b/localization/strings/da-DK/Resources.resw @@ -744,6 +744,11 @@ Besøg https://aka.ms/wslinstall for at få flere oplysninger Kørsel af fejlfindingsshell kræver, at der køres wsl.exe som administrator. + + Windows Admin Protection er aktiveret, og dine uddelinger kan være registreret under en anden konto. +Du kan få flere oplysninger om Admin Protection ved at besøge https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Installationsprocessen for distributionen '{}' mislykkedes med returkoden: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Går tilbage til NAT-netværk. Windows-version {}. {} har ikke de nødvendige funktioner {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 er deaktiveret på værten på en ikke-understøttet måde (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V-firewall understøttes ikke @@ -1877,6 +1886,41 @@ Du kan også få adgang til flere Fjernindstillinger for VS-kode via kommandopal Det antal millisekunder, en VM er inaktiv, før den lukkes. + + Anvend ændringer + + + Nedlukning af WSL påkrævet + + + Ændrede WSL-indstillinger kræver WSL-lukning og genstart for at fuldføre. Hvis du lukker, afbrydes alle aktive WSL-sessioner. + + + Luk WSL ned nu + + + Senere + + + Kunne ikke anvende ændringer + + + Luk + + + De ventende ændringer af indstillingerne kunne ikke gemmes. Fejlkode: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Indstillingerne blev gemt, men WSL kunne ikke lukkes: {0} + {Locked="{0}"}Format placeholder for exception message + + + Til + + + Fra + Byg, kør, foretag fejlfinding og profilér dine apps, der kører på WSL, fra Visual Studio diff --git a/localization/strings/de-DE/Resources.resw b/localization/strings/de-DE/Resources.resw index 719cabe18..dc28f4aaa 100644 --- a/localization/strings/de-DE/Resources.resw +++ b/localization/strings/de-DE/Resources.resw @@ -750,6 +750,11 @@ Weitere Informationen finden Sie unter https://aka.ms/wslinstall Zum Ausführen der Debug-Shell muss wsl.exe als Administrator ausgeführt werden. + + Windows Admin Protection ist aktiviert, und Ihre Verteilungen werden möglicherweise unter einem anderen Konto registriert. +Weitere Informationen zu Admin Protection finden Sie unter https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Fehler beim Installationsprozess für die Distribution „{}“. Exitcode: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ Fallback auf NAT-Netzwerk. Windows-Version {}. {} verfügt nicht über die erforderlichen Features. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 ist auf dem Host auf eine nicht unterstützte Weise deaktiviert (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V-Firewall wird nicht unterstützt @@ -1883,6 +1892,41 @@ Sie können auch über die Befehlspalette in VS Code selbst auf weitere VS Code Die Anzahl von Millisekunden, die eine VM im Leerlauf ist, bevor sie heruntergefahren wird. + + Änderungen anwenden + + + WSL-Herunterfahren erforderlich + + + Geänderte WSL-Einstellungen erfordern ein Herunterfahren und einen Neustart von WSL, um abgeschlossen zu werden. Das Herunterfahren beendet alle aktiven WSL-Sitzungen. + + + WSL jetzt herunterfahren + + + Später + + + Änderungen können nicht angewendet werden. + + + Schließen + + + Fehler beim Speichern ausstehender Einstellungsänderungen. Fehlercode: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Die Einstellungen wurden gespeichert, aber beim Herunterfahren der WSL ist ein Fehler aufgetreten: {0} + {Locked="{0}"}Format placeholder for exception message + + + Ein + + + Aus + Erstellen, Ausführen, Debuggen und Profilieren von Apps, die auf WSL laufen, direkt in Visual Studio diff --git a/localization/strings/en-GB/Resources.resw b/localization/strings/en-GB/Resources.resw index 12a4e984a..5e8a11d0d 100644 --- a/localization/strings/en-GB/Resources.resw +++ b/localization/strings/en-GB/Resources.resw @@ -744,6 +744,11 @@ For information please visit https://aka.ms/wslinstall Running the debug shell requires running wsl.exe as Administrator. + + Windows Admin Protection is enabled and your distributions may be registered under a different account. +For more information on Admin Protection, please visit https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + The installation process for distribution '{}' failed with exit code: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Falling back to NAT networking. Windows version {}.{} does not have the required features {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 is disabled on the host in an unsupported way (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V firewall is not supported @@ -1877,6 +1886,41 @@ You can also access more VS Code Remote options through the command palette with The number of milliseconds that a VM is idle, before it is shut down. + + Apply changes + + + WSL Shut-down Required + + + Changed WSL settings require a WSL shut-down and restart to finalise. Shutting down will cause any active WSL sessions to terminate. + + + Shut-down WSL now + + + Later + + + Unable to apply changes + + + Close + + + Failed to save pending settings changes. Error code: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Settings were saved, but shutting down WSL failed: {0} + {Locked="{0}"}Format placeholder for exception message + + + On + + + Off + Build, run, debug, and profile your apps running on WSL from Visual Studio diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index de258d29d..639d4b79d 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -741,6 +741,11 @@ For information please visit https://aka.ms/wslinstall Running the debug shell requires running wsl.exe as Administrator. + + Windows Admin Protection is enabled and your distributions may be registered under a different account. +For more information on Admin Protection, please visit https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + The installation process for distribution '{}' failed with exit code: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -880,6 +885,10 @@ Falling back to NAT networking. Windows version {}.{} does not have the required features {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 is disabled on the host in an unsupported way (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V firewall is not supported @@ -1878,6 +1887,41 @@ You can also access more VS Code Remote options through the command palette with The number of milliseconds that a VM is idle, before it is shut down. + + Apply changes + + + WSL Shutdown Required + + + Changed WSL settings require a WSL shutdown and restart to finalize. Shutting down will cause any active WSL sessions to terminate. + + + Shutdown WSL now + + + Later + + + Unable to apply changes + + + Close + + + Failed to save pending settings changes. Error code: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Settings were saved, but shutting down WSL failed: {0} + {Locked="{0}"}Format placeholder for exception message + + + On + + + Off + Build, run, debug, and profile your apps running on WSL from Visual Studio diff --git a/localization/strings/es-ES/Resources.resw b/localization/strings/es-ES/Resources.resw index 4edcbb9c3..31c0602d9 100644 --- a/localization/strings/es-ES/Resources.resw +++ b/localization/strings/es-ES/Resources.resw @@ -750,6 +750,11 @@ Para obtener información, visite https://aka.ms/wslinstall La ejecución del shell de depuración requiere ejecutar wsl.exe como administrador. + + Windows Admin Protection está habilitada y es posible que las distribuciones se registren en otra cuenta. +Para obtener más información sobre Admin Protection, visite https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Error en el proceso de instalación de la distribución "{}". Código de salida: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ Revirtiendo a las redes NAT. Versión {}de Windows. {} no tiene las características necesarias {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 está deshabilitado en el host de forma no compatible (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + No se admite el firewall de Hyper-V @@ -1883,6 +1892,41 @@ También puedes acceder a más opciones remotas de VS Code mediante la paleta d Número de milisegundos que una VM está inactiva antes de apagarse. + + Aplicar cambios + + + Apagado de WSL requerido + + + La configuración de WSL modificada requiere un apagado y reinicio de WSL para finalizar. Si se cierra, finalizarán todas las sesiones de WSL activas. + + + Apagar WSL ahora + + + Más tarde + + + No se pueden aplicar los cambios + + + Cerrar + + + No se pudieron guardar los cambios de configuración pendientes. Código de error: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Se guardó la configuración, pero se produjo un error al cerrar WSL: {0} + {Locked="{0}"}Format placeholder for exception message + + + Activado + + + Desactivado + Compila, ejecuta, depura y analiza el perfil de tus aplicaciones que se ejecutan en WSL desde Visual Studio diff --git a/localization/strings/fi-FI/Resources.resw b/localization/strings/fi-FI/Resources.resw index 67b5a6ea8..16f058d48 100644 --- a/localization/strings/fi-FI/Resources.resw +++ b/localization/strings/fi-FI/Resources.resw @@ -744,6 +744,11 @@ Saat lisätietoja osoitteesta https://aka.ms/wslinstall Virheenkorjausliittymän suorittaminen edellyttää wsl.exe suorittamista järjestelmänvalvojana. + + Windows Admin Protection on käytössä, ja jakelusi voidaan rekisteröidä toiselle tilille. +Lisätietoja Admin Protection saat osoitteesta https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Jakelun {} asennusprosessi epäonnistui, lopetuskoodi: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Palataan nat-verkkopalveluun. Windows-versio {}. {} ei sisällä tarvittavia ominaisuuksia {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 on poistettu käytöstä isäntäkoneessa ei tuetulla tavalla (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V-palomuuria ei tueta @@ -1877,6 +1886,41 @@ Voit käyttää myös muita VS Code Remote -asetuksia VS Coden komentovalikoiman Virtuaalikoneen käyttämättömyysaika millisekunteina, ennen kuin se sammutetaan. + + Ota muutokset käyttöön + + + WSL:n sammutus tarvitaan + + + Muutetut WSL-asetukset vaativat WSL:n sulkemisen ja uudelleenkäynnistyksen, jotta muutokset tulevat voimaan. Sulkeminen lopettaa kaikki aktiiviset WSL-istunnot. + + + Sammuta WSL nyt + + + Myöhemmin + + + Muutosten käyttöönotto epäonnistui + + + Sulje + + + Odottavien asetusten muutosten tallentaminen epäonnistui. Virhekoodi: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Asetukset tallennettiin, mutta WSL:n sammuttaminen epäonnistui: {0} + {Locked="{0}"}Format placeholder for exception message + + + Käytössä + + + Ei käytössä + Kehitä, suorita ja profiloi sovelluksiasi, jotka toimivat WSL:ssä, sekä korjaa niiden virheitä Visual Studion kautta diff --git a/localization/strings/fr-FR/Resources.resw b/localization/strings/fr-FR/Resources.resw index f4b881a09..42747d805 100644 --- a/localization/strings/fr-FR/Resources.resw +++ b/localization/strings/fr-FR/Resources.resw @@ -751,6 +751,11 @@ Pour plus d’informations, visitez https://aka.ms/wslinstall L'exécution du shell de débogage nécessite l'exécution de wsl.exe en tant qu'administrateur. + + Windows Admin Protection est activée et vos distributions peuvent être enregistrées sous un autre compte. +Pour plus d’informations sur Admin Protection, veuillez consulter https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Le processus d’installation de la distribution « {} » a échoué avec le code de sortie : {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -886,6 +891,10 @@ Retour à la mise en réseau NAT. Windows version {}. {} ne dispose pas des fonctionnalités requises {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 est désactivé sur l’hôte de manière non prise en charge (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Le pare-feu Hyper-V n’est pas pris en charge @@ -1884,6 +1893,41 @@ Vous pouvez également accéder à davantage d'options VS Code Remote via la pal Nombre de millisecondes pendant lesquelles une machine virtuelle est inactive, avant d’être éteinte. + + Appliquer les modifications + + + Arrêt de WSL requis + + + Les paramètres WSL modifiés nécessitent un arrêt et un redémarrage WSL pour finalisation. L’arrêt entraîne l’arrêt de toutes les sessions WSL actives. + + + Arrêter WSL maintenant + + + Plus tard + + + Nous ne pouvons pas appliquer les modifications + + + Fermer + + + Échec de l’enregistrement des modifications des paramètres en attente. Code d’erreur : {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Les paramètres ont été enregistrés, mais l’arrêt de WSL a échoué : {0} + {Locked="{0}"}Format placeholder for exception message + + + Activé + + + Désactivé + Créer, exécuter, déboguer et profiler vos applications exécutées sur WSL dans Visual Studio diff --git a/localization/strings/hu-HU/Resources.resw b/localization/strings/hu-HU/Resources.resw index 3ca74a9e6..3f402aa6e 100644 --- a/localization/strings/hu-HU/Resources.resw +++ b/localization/strings/hu-HU/Resources.resw @@ -744,6 +744,11 @@ További információért látogasson el a https://aka.ms/wslinstall A hibakeresési felület futtatásához rendszergazdai wsl.exe fájlt kell futtatni. + + Windows Admin Protection engedélyezve van, és előfordulhat, hogy a disztribúciók egy másik fiókban vannak regisztrálva. +A Admin Protection kapcsolatos további információért látogasson el a https://aka.ms/apdevguide oldalra + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + A(z) {} disztribúció telepítési folyamata a következő kilépési kóddal meghiúsult: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Visszaállás NAT-hálózatkezelésre. Windows-verzió: {}. {} nem rendelkezik a szükséges funkciókkal {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Az IPv6 nem támogatott módon van letiltva a gazdagépen (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + A Hyper-V tűzfal nem támogatott @@ -1877,6 +1886,41 @@ A VS Code-ban található parancskatalógusban további VS Code Remote beállít A virtuális gép üresjárati ideje ezredmásodpercben a leállítás előtt. + + Módosítások alkalmazása + + + Le kell állítani a WSL-t + + + A módosított WSL-beállítások véglegesítéséhez le kell állítani és újra kell indítani a WSL-t. A leállítás megszakítja az összes aktív WSL-munkamenetet. + + + WSL leállítása most + + + Később + + + A módosítások alkalmazása nem sikerült + + + Bezárás + + + Nem sikerült menteni a függőben lévő beállítások módosításait. Hibakód: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + A beállítások mentése megtörtént, de a WSL leállítása nem sikerült: {0} + {Locked="{0}"}Format placeholder for exception message + + + Bekapcsolva + + + Kikapcsolva + WSL-en futó alkalmazásokat készíthet, futtathat, profilozhat és végezhet rajtuk hibakeresést a Visual Studio segítségével diff --git a/localization/strings/it-IT/Resources.resw b/localization/strings/it-IT/Resources.resw index 7a9d895c4..48e5d09f1 100644 --- a/localization/strings/it-IT/Resources.resw +++ b/localization/strings/it-IT/Resources.resw @@ -750,6 +750,11 @@ Per informazioni, visitare https://aka.ms/wslinstall L'esecuzione della shell di debug richiede l'esecuzione wsl.exe come amministratore. + + Windows Admin Protection è abilitato e le distribuzioni possono essere registrate con un account diverso. +Per ulteriori informazioni su Admin Protection, visitare https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Il processo di installazione per la distribuzione '{}' non è riuscito con codice di uscita: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ Fallback alla rete NAT. Versione di Windows {}. {} non dispone delle funzionalità necessarie {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 è disabilitato sull'host in modo non supportato (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Il firewall Hyper-V non è supportato @@ -1883,6 +1892,41 @@ Puoi anche accedere a più opzioni remote di VS Code tramite il riquadro comandi Numero di millisecondi di inattività di una macchina virtuale prima dell'arresto. + + Applica modifiche + + + Arresto WSL richiesto + + + Le modifiche alle impostazioni di WSL richiedono un arresto e un riavvio di WSL per essere finalizzate. L'arresto causerà la terminazione di eventuali sessioni WSL attive. + + + Arresta WSL adesso + + + Più tardi + + + Non è possibile applicare le modifiche + + + Chiudi + + + Non è stato possibile salvare le modifiche alle impostazioni in sospeso. Codice errore: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Le impostazioni sono state salvate, ma l'arresto di WSL non è riuscito: {0} + {Locked="{0}"}Format placeholder for exception message + + + Attivato + + + Disattivato + Compila, esegui, esegui il debug e profila le app in esecuzione in WSL da Visual Studio diff --git a/localization/strings/ja-JP/Resources.resw b/localization/strings/ja-JP/Resources.resw index c911e47ea..0e420b3b3 100644 --- a/localization/strings/ja-JP/Resources.resw +++ b/localization/strings/ja-JP/Resources.resw @@ -750,6 +750,11 @@ Windows Update または {} 経由で必要な更新プログラムをインス デバッグ シェルを実行するには、wsl.exe を管理者として実行する必要があります。 + + Windows Admin Protection が有効になり、ディストリビューションが別のアカウントに登録されている可能性があります。 +Admin Protection の詳細については、https://aka.ms/apdevguide をご覧ください + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + ディストリビューション '{}' のインストール プロセスが次の終了コードで失敗しました: {}。 {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ NAT ネットワークにフォールバックしています。 Windows バージョン {}.{} には必要な機能がありません {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 が、サポートされていない方法でホスト上で無効になっています (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V ファイアウォールはサポートされていません @@ -1883,6 +1892,41 @@ VS Code 自体のコマンド パレットから、より多くの VS Code リ VM がシャットダウンされるまでのアイドル時間 (ミリ秒) です。 + + 変更の適用 + + + WSL のシャットダウンが必要です + + + WSL 設定の変更を完了するには、WSL のシャットダウンと再起動が必要です。シャットダウンすると、アクティブな WSL セッションがすべて終了します。 + + + WSL を今すぐシャットダウン + + + 後で + + + 変更を適用できません + + + 閉じる + + + 保留中の設定変更を保存できませんでした。エラー コード: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + 設定は保存されましたが、WSL のシャットダウンに失敗しました: {0} + {Locked="{0}"}Format placeholder for exception message + + + オン + + + オフ + Visual Studio から WSL 上で動作するアプリをビルド、実行、デバッグ、プロファイル diff --git a/localization/strings/ko-KR/Resources.resw b/localization/strings/ko-KR/Resources.resw index 0cfcd26cb..fa3bbc47f 100644 --- a/localization/strings/ko-KR/Resources.resw +++ b/localization/strings/ko-KR/Resources.resw @@ -750,6 +750,11 @@ Windows 업데이트를 통해 또는 다음을 통해 필요한 업데이트 디버그 셸을 실행하려면 wsl.exe 관리자 권한으로 실행해야 합니다. + + Windows Admin Protection이 활성화되었으며, 배포가 다른 계정에 등록될 수 있습니다. +Admin Protection에 대해 자세히 알아보려면 https://aka.ms/apdevguide를 방문하세요. + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + 배포 '{}'에 대한 설치 프로세스가 종료 코드 {}(으)로 인해 실패했습니다. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ NAT 네트워킹으로 대체합니다. Windows 버전 {}. {}에 필요한 기능이 없습니다. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + 호스트에서 지원하지 않는 방식으로 IPv6가 비활성화되어 있습니다(HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff). + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V 방화벽은 지원되지 않습니다. @@ -1883,6 +1892,41 @@ VS Code 자체 내의 명령 팔레트를 통해 더 많은 VS Code 원격 옵 VM이 종료되기 전에 유휴 상태인 시간(밀리초)입니다. + + 변경 내용 적용 + + + WSL 종료 필요 + + + 변경된 WSL 설정을 완료하려면 WSL을 종료하고 다시 시작해야 합니다. 종료하면 활성 WSL 세션이 종료됩니다. + + + 지금 WSL 종료 + + + 나중에 + + + 변경 내용을 적용할 수 없음 + + + 닫기 + + + 보류 중인 설정 변경 내용을 저장하지 못했습니다. 오류 코드: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + 설정이 저장되었지만 WSL을 종료하지 못했습니다. {0} + {Locked="{0}"}Format placeholder for exception message + + + 켜짐 + + + 꺼짐 + Visual Studio에서 WSL로 실행 중인 앱을 빌드, 실행, 디버그 및 프로파일링하기 diff --git a/localization/strings/nb-NO/Resources.resw b/localization/strings/nb-NO/Resources.resw index 3d8d8002f..4cde61dd2 100644 --- a/localization/strings/nb-NO/Resources.resw +++ b/localization/strings/nb-NO/Resources.resw @@ -744,6 +744,11 @@ Hvis du vil ha mer informasjon, kan du gå til https://aka.ms/wslinstall Kjøring av feilsøkingsskallet krever kjøring wsl.exe som administrator. + + Windows Admin Protection er aktivert, og distribusjonene kan registreres under en annen konto. +Hvis du vil ha mer informasjon om Admin Protection, kan du gå til https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Installasjonsprosessen for distribusjonen {} mislyktes med avslutningskoden: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Faller tilbake til NAT-nettverk. Windows-versjon {}. {} har ikke de nødvendige funksjonene {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 er deaktivert på verten på en ikke-støttet måte (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Brannmuren støtter ikke Hyper-V @@ -1877,6 +1886,41 @@ Du kan også åpne flere eksterne alternativer for VS Code gjennom kommandopalet Antall millisekunder en virtuell maskin er inaktiv, før den avsluttes. + + Bruk endringer + + + WSL-avslutning kreves + + + Endrede WSL-innstillinger krever WSL-avslutning og omstart for å fullføre. Hvis du avslutter, avsluttes alle aktive WSL-økter. + + + Avslutt WSL nå + + + Senere + + + Kan ikke ta i bruk endringene + + + Lukk + + + Kan ikke lagre ventende innstillingsendringer. Feilkode: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Innstillingene ble lagret, men avslutning av WSL mislyktes: {0} + {Locked="{0}"}Format placeholder for exception message + + + + + + Av + Bygg, kjør, feilsøk og profiler apper som kjører på WSL fra Visual Studio diff --git a/localization/strings/nl-NL/Resources.resw b/localization/strings/nl-NL/Resources.resw index 13dc23ebd..903fced0b 100644 --- a/localization/strings/nl-NL/Resources.resw +++ b/localization/strings/nl-NL/Resources.resw @@ -744,6 +744,11 @@ Ga naar https://aka.ms/wslinstall voor meer informatie Als u de foutopsporingsshell wilt uitvoeren, moet u wsl.exe als beheerder uitvoeren. + + Windows Admin Protection is ingeschakeld en uw uitkeringen kunnen onder een ander account worden geregistreerd. +Ga voor meer informatie over Admin Protection naar https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Het installatieproces voor distributie {} is mislukt met afsluitcode: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Terugvallen op NAT-netwerken. Windows-versie {}. {} beschikt niet over de vereiste functies {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 is op de host uitgeschakeld op een niet-ondersteunde manier (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V-firewall wordt niet ondersteund @@ -1877,6 +1886,41 @@ U hebt ook toegang tot meer externe VS Code-opties via het opdrachtpalet in VS C Het aantal milliseconden dat een VM inactief is voordat deze wordt afgesloten. + + Wijzigingen toepassen + + + WSL-afsluiting vereist + + + Voor gewijzigde WSL-instellingen is het afsluiten en opnieuw opstarten van WSL vereist. Als u afsluit, worden actieve WSL-sessies beëindigd. + + + WSL nu afsluiten + + + Later + + + Kan wijzigingen niet toepassen + + + Sluiten + + + Kan de in behandeling zijnde instellingswijzigingen niet opslaan. Foutcode: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Instellingen zijn opgeslagen, maar WSL kan niet worden afgesloten: {0} + {Locked="{0}"}Format placeholder for exception message + + + Aan + + + Uit + Apps die op WSL draaien vanuit Visual Studio maken, uitvoeren, profileren en fouten opsporen diff --git a/localization/strings/pl-PL/Resources.resw b/localization/strings/pl-PL/Resources.resw index a0f40b411..a7a657587 100644 --- a/localization/strings/pl-PL/Resources.resw +++ b/localization/strings/pl-PL/Resources.resw @@ -744,6 +744,11 @@ Aby uzyskać informacje, odwiedź stronę https://aka.ms/wslinstall Uruchomienie powłoki debugowania wymaga uruchomienia programu wsl.exe jako administrator. + + Windows Admin Protection jest włączona, a Twoje dystrybucje mogą być zarejestrowane na innym koncie. +Więcej informacji na temat Admin Protection można znaleźć na stronie https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Proces instalacji dystrybucji „{}” nie powiódł się. Kod zakończenia: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Powrót do sieci NAT. Wersja systemu Windows {}. {} nie ma wymaganych funkcji {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Protokół IPv6 na hoście został wyłączony w nieobsługiwany sposób (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Zapora funkcji Hyper-V nie jest obsługiwana @@ -1877,6 +1886,41 @@ Możesz również uzyskać dostęp do większej liczby opcji zdalnych programu V Liczba milisekund bezczynności maszyny wirtualnej przed jej zamknięciem. + + Zastosuj zmiany + + + Wymagane zamknięcie systemu WSL + + + Zmienione ustawienia systemu WSL wymagają zamknięcia i ponownego uruchomienia systemu WSL w celu sfinalizowania. Zamknięcie spowoduje przerwanie wszystkich aktywnych sesji systemu WSL. + + + Zamknij system WSL teraz + + + Później + + + Nie można zastosować zmian + + + Zamknij + + + Nie można zapisać oczekujących zmian ustawień. Kod błędu: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Ustawienia zostały zapisane, ale zamknięcie systemu WSL nie powiodło się: {0} + {Locked="{0}"}Format placeholder for exception message + + + Włączony + + + Wyłączony + Kompiluj, uruchamiaj, debuguj i profiluj aplikacje działające w systemie WSL z poziomu programu Visual Studio diff --git a/localization/strings/pt-BR/Resources.resw b/localization/strings/pt-BR/Resources.resw index 9ba5274a8..f373c2384 100644 --- a/localization/strings/pt-BR/Resources.resw +++ b/localization/strings/pt-BR/Resources.resw @@ -751,6 +751,11 @@ Para obter informações, visite https://aka.ms/wslinstall A execução do shell de depuração requer wsl.exe como Administrador. + + Windows Admin Protection está habilitado e suas distribuições podem ser registradas em uma conta diferente. +Para obter mais informações sobre Admin Protection, visite https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + O processo de instalação da distribuição '{}' falhou com o código de saída: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -886,6 +891,10 @@ Voltando à rede NAT. Versão do Windows {}. {} não tem os recursos necessários {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + O IPv6 está desabilitado no host de uma forma sem suporte (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Não há suporte para o firewall do Hyper-V @@ -1884,6 +1893,41 @@ Você também pode acessar mais opções do VS Code Remote através da paleta de O número de milissegundos em que uma VM está ociosa, antes de ser desligada. + + Aplicar alterações + + + É necessário desligar o WSL + + + As configurações alteradas do WSL exigem um desligamento do WSL e reinicialização para finalizar. O desligamento fará com que todas as sessões ativas do WSL sejam encerradas. + + + Desligar o WSL agora + + + Mais tarde + + + Não foi possível aplicar alterações + + + Fechar + + + Falha ao salvar alterações de configurações pendentes. Código de erro: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + As configurações foram salvas, mas o desligamento do WSL falhou: {0} + {Locked="{0}"}Format placeholder for exception message + + + Ligado + + + Desligado + Crie, execute, depure e crie o perfil de seus aplicativos em execução no WSL no Visual Studio diff --git a/localization/strings/pt-PT/Resources.resw b/localization/strings/pt-PT/Resources.resw index e91066245..7e6dc7aa8 100644 --- a/localization/strings/pt-PT/Resources.resw +++ b/localization/strings/pt-PT/Resources.resw @@ -744,6 +744,11 @@ Para obter informações, visite https://aka.ms/wslinstall A execução da shell de depuração requer a execução de wsl.exe como Administrador. + + Windows Admin Protection está ativada e as suas distribuições podem ser registadas numa conta diferente. +Para mais informações sobre Admin Protection, visite https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + O processo de instalação da distribuição "{}" falhou com o código de saída: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ A reverter para a rede NAT. Versão do Windows {}. {} não tem as funcionalidades necessárias {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + O IPv6 está desativado no anfitrião de uma forma não suportada (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + A firewall do Hyper-V não é suportada @@ -1877,6 +1886,41 @@ Também pode aceder a mais Opções remotas do VS Code através da paleta de com O número de milissegundos em que uma VM está inativa antes de ser encerrada. + + Aplicar alterações + + + É Necessário Encerrar o WSL + + + As definições do WSL alteradas requerem o encerramento e o reinício do WSL para serem concluídas. O encerramento fará com que quaisquer sessões WSL ativas terminem. + + + Encerrar o WSL agora + + + Mais tarde + + + Não é possível aplicar as alterações + + + Fechar + + + Falha ao guardar alterações de definições pendentes. Código de erro: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + As definições foram guardadas, mas o encerramento do WSL falhou: {0} + {Locked="{0}"}Format placeholder for exception message + + + Ativado + + + Desativado + Crie, execute, depure e defina perfis para as suas aplicações em execução no WSL a partir do Visual Studio diff --git a/localization/strings/ru-RU/Resources.resw b/localization/strings/ru-RU/Resources.resw index 07d59e7c9..0a4157a02 100644 --- a/localization/strings/ru-RU/Resources.resw +++ b/localization/strings/ru-RU/Resources.resw @@ -751,6 +751,11 @@ Для запуска оболочки отладки требуется запуск wsl.exe от имени администратора. + + Windows Admin Protection включена, а ваши дистрибутивы, возможно, зарегистрированы под другой учетной записью. +Подробнее о Admin Protection можно узнать на https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Сбой процесса установки для распространения "{}" с кодом завершения: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -886,6 +891,10 @@ Версия Windows {}. У {} нет необходимых компонентов {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 отключен в узле неподдерживаемым способом (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Брандмауэр Hyper-V не поддерживается @@ -1884,6 +1893,41 @@ wsl.exe --manage <DistributionName> --set-sparse true --allow-unsafe Число миллисекунд бездействия виртуальной машины до ее завершения. + + Применить изменения + + + Требуется завершить работу WSL + + + Измененные параметры WSL требуют завершения работы и перезапуска WSL для их применения. Завершение работы приведет к прекращению всех активных сеансов WSL. + + + Завершить работу WSL сейчас + + + Позже + + + Не удалось применить изменения + + + Закрыть + + + Не удалось сохранить ожидающие изменения параметров. Код ошибки: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Параметры сохранены, но не удалось завершить работу WSL: {0} + {Locked="{0}"}Format placeholder for exception message + + + Включено + + + Отключено + Создавайте, запускайте, отлаживайте и профилируйте приложения на WSL из Visual Studio diff --git a/localization/strings/sv-SE/Resources.resw b/localization/strings/sv-SE/Resources.resw index ec80f1e9c..b6cb64f95 100644 --- a/localization/strings/sv-SE/Resources.resw +++ b/localization/strings/sv-SE/Resources.resw @@ -744,6 +744,11 @@ Mer information finns på https://aka.ms/wslinstall Om du kör felsökningsgränssnittet måste du köra wsl.exe som administratör. + + Windows Admin Protection är aktiverat och dina distributioner kan registreras under ett annat konto. +Mer information om Admin Protection finns på https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + Installationsprocessen för distributionen '{}' misslyckades med slutkoden: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ Mer information finns på https://aka.ms/wslinstall Windows-version {}.{} har inte de nödvändiga funktionerna {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6 är inaktiverat på värden på ett sätt som inte stöds (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V-brandväggen stöds inte @@ -1877,6 +1886,41 @@ Du kan också komma åt fler VS Code-fjärralternativ via kommandopaletten inom Antalet millisekunder som en virtuell dator är inaktiv innan den stängs av. + + Tillämpa ändringar + + + WSL-avstängning krävs + + + Ändrade WSL-inställningar kräver en WSL-avstängning och omstart för att slutföra. Om du stänger av avslutas alla aktiva WSL-sessioner. + + + Stäng av WSL nu + + + Senare + + + Det gick inte att tillämpa ändringarna + + + Stäng + + + Det gick inte att spara väntande inställningsändringar. Felkod: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Inställningarna sparades, men det gick inte att stänga WSL: {0} + {Locked="{0}"}Format placeholder for exception message + + + + + + Av + Skapa, kör, felsök och profilera dina appar som körs på WSL direkt från Visual Studio diff --git a/localization/strings/tr-TR/Resources.resw b/localization/strings/tr-TR/Resources.resw index 4af0a8b18..759053a90 100644 --- a/localization/strings/tr-TR/Resources.resw +++ b/localization/strings/tr-TR/Resources.resw @@ -744,6 +744,11 @@ Daha fazla bilgi için lütfen https://aka.ms/wslinstall adresini ziyaret edin.< Hata ayıklama kabuğu çalıştırmak için yönetici wsl.exe çalıştırmanız gerekir. + + Windows Admin Protection etkinleştirildi ve dağıtımlarınız farklı bir hesap altında kayıtlı olabilir. +Admin Protection hakkında daha fazla bilgi için lütfen https://aka.ms/apdevguide adresini ziyaret edin + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + '{}' dağıtımı için yükleme işlemi şu çıkış koduyla başarısız oldu: {}. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -879,6 +884,10 @@ NAT ağ bağlantısına geri dönülüyor. Windows sürümü {}. {} gerekli özelliklere sahip değil {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + IPv6, ana bilgisayarda desteklenmeyen bir şekilde devre dışı bırakıldı (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V güvenlik duvarı desteklenmiyor @@ -1877,6 +1886,41 @@ Ayrıca VS Code'un içindeki komut paleti aracılığıyla daha fazla VS Code Re Bir VM'nin kapatılmadan önce boşta olduğu milisaniye sayısı. + + Değişiklikleri uygula + + + WSL'nin Kapatılması Gerekiyor + + + Değiştirilen WSL ayarlarının geçerli hale gelmesi için WSL'nin kapatılıp yeniden başlatılması gerekir. Kapatma işlemi, aktif olan tüm WSL oturumlarının sonlanmasına yol açar. + + + WSL'yi şimdi kapatın + + + Daha sonra + + + Değişiklikler uygulanamıyor + + + Kapat + + + Bekleyen ayar değişiklikleri kaydedilemedi. Hata kodu: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + Ayarlar kaydedildi, ancak WSL kapatılamadı: {0} + {Locked="{0}"}Format placeholder for exception message + + + Açık + + + Kapalı + Visual Studio'dan WSL üzerinde çalışan uygulamalarınızı derleyin, çalıştırın, hatalarını ayıklayın ve profillerini oluşturun diff --git a/localization/strings/zh-CN/Resources.resw b/localization/strings/zh-CN/Resources.resw index 7a674d4e0..6efcec29b 100644 --- a/localization/strings/zh-CN/Resources.resw +++ b/localization/strings/zh-CN/Resources.resw @@ -750,6 +750,11 @@ Windows: {} 运行调试 shell 需要以管理员身份运行 wsl.exe。 + + Windows Admin Protection 已启用,并且你的分发可能会在其他帐户下注册。 +有关 Admin Protection 的详细信息,请访问 https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + 分发“{}”的安装过程失败,退出代码: {}。 {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ Windows: {} Windows 版本 {}。{} 没有所需的功能 {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + 主机上以不受支持的方式禁用了 IPv6 (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + Hyper-V 防火墙不受支持 @@ -1883,6 +1892,41 @@ wsl.exe --manage <DistributionName> --set-sparse true --allow-unsafe VM 在关闭之前处于空闲状态的毫秒数。 + + 应用更改 + + + 需要关闭 WSL + + + 更改的 WSL 设置需要关闭 WSL 并重启才能完成。关闭将导致任何活动的 WSL 会话终止。 + + + 立即关闭 WSL + + + 以后再说 + + + 无法应用更改 + + + 关闭 + + + 未能保存挂起的设置更改。错误代码: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + 已保存设置,但关闭 WSL 失败: {0} + {Locked="{0}"}Format placeholder for exception message + + + + + + + 通过 Visual Studio 构建、运行、调试和分析运行于 WSL 上的应用 diff --git a/localization/strings/zh-TW/Resources.resw b/localization/strings/zh-TW/Resources.resw index d0ddf6629..7f04ef0be 100644 --- a/localization/strings/zh-TW/Resources.resw +++ b/localization/strings/zh-TW/Resources.resw @@ -750,6 +750,11 @@ Windows 版本: {} 執行偵錯殼層需要以系統管理員身分執行 wsl.exe。 + + Windows Admin Protection 已啟用,您的發佈可能會在其他帳戶下註冊。 +如需 Admin Protection 的詳細資訊,請瀏覽 https://aka.ms/apdevguide + {Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated + 發佈 '{}' 的安裝程序失敗,結束代碼: {}。 {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -885,6 +890,10 @@ Windows 版本: {} Windows 版本 {}。{} 沒有所需的功能 {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + 在主機上以不受支援的方式停用 IPv6 (HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents=0xff) + {Locked="HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"}{Locked="DisabledComponents"}{Locked="0xff"} + 不支援 Hyper-V 防火牆 @@ -1883,6 +1892,41 @@ wsl.exe --manage <DistributionName> --set-sparse true --allow-unsafe VM 在關機前閒置的毫秒數。 + + 套用變更 + + + 需要關閉 WSL + + + 變更的 WSL 設定需要關閉 WSL 並重新啟動才能完成。關機會導致所有使用中的 WSL 工作階段終止。 + + + 立即關閉 WSL + + + 稍後 + + + 無法套用變更 + + + 關閉 + + + 無法儲存擱置中的設定變更。錯誤碼: {0} + {Locked="{0}"}Format placeholder for Win32 error code + + + 已儲存設定,但關閉 WSL 失敗: {0} + {Locked="{0}"}Format placeholder for exception message + + + 開啟 + + + 關閉 + 從 Visual Studio 組建、執行、偵錯並分析在 WSL 上執行的應用程式 diff --git a/src/linux/inc/lxwil.h b/src/linux/inc/lxwil.h index e5f5c2f04..2871dc6cc 100644 --- a/src/linux/inc/lxwil.h +++ b/src/linux/inc/lxwil.h @@ -223,7 +223,7 @@ inline int ResultFromCaughtException() #define THROW_LAST_ERROR() THROW_ERRNO(errno); #define THROW_INVALID() THROW_ERRNO(EINVAL) -#define THROW_UNEXCEPTED() THROW_ERRNO(EINVAL) +#define THROW_UNEXPECTED() THROW_ERRNO(EINVAL) #define THROW_INVALID_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) #define THROW_UNEXPECTED_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) @@ -442,7 +442,7 @@ class unique_pipe static unique_pipe create(int flags) { int pipe[2] = {-1, -1}; - if (pipe2(pipe, flags) < -1) + if (pipe2(pipe, flags) < 0) { THROW_ERRNO(errno); } diff --git a/src/linux/init/DnsServer.cpp b/src/linux/init/DnsServer.cpp index cf5cac9df..dcad65349 100644 --- a/src/linux/init/DnsServer.cpp +++ b/src/linux/init/DnsServer.cpp @@ -193,7 +193,6 @@ try // whenever there is new data on the TCP connection. epoll_event event{}; event.events = EPOLLIN; - event.data.fd = localContext->m_tcpConnection.get(); event.data.ptr = localContext.get(); Syscall(epoll_ctl, m_epollFd.get(), EPOLL_CTL_ADD, localContext->m_tcpConnection.get(), &event); diff --git a/src/linux/init/GnsEngine.cpp b/src/linux/init/GnsEngine.cpp index c0d826daf..b8128c1a3 100644 --- a/src/linux/init/GnsEngine.cpp +++ b/src/linux/init/GnsEngine.cpp @@ -25,12 +25,13 @@ constexpr auto c_ipStrings = {"ip", "ip6"}; const char* c_loopbackInterfaceName = "lo"; GnsEngine::GnsEngine( + wsl::shared::SocketChannel& channel, const NotificationRoutine& notificationRoutine, const StatusRoutine& statusRoutine, NetworkManager& manager, std::optional dnsTunnelingFd, const std::string& dnsTunnelingIpAddress) : - notificationRoutine(notificationRoutine), statusRoutine(statusRoutine), manager(manager) + channel(channel), notificationRoutine(notificationRoutine), statusRoutine(statusRoutine), manager(manager) { if (dnsTunnelingFd.has_value()) { @@ -364,11 +365,11 @@ void GnsEngine::ProcessLinkChange(Interface& interface, const wsl::shared::hns:: } } -std::tuple GnsEngine::ProcessNextMessage() +std::tuple GnsEngine::ProcessNextMessage(wsl::shared::Transaction& transaction) { int return_value = 0; - auto payload = notificationRoutine(); + auto payload = notificationRoutine(transaction); if (!payload.has_value()) { GNS_LOG_ERROR("Received empty message, exiting"); @@ -724,22 +725,23 @@ void GnsEngine::run() while (true) { + auto transaction = channel.ReceiveTransaction(); try { GNS_LOG_INFO("Processing Next Message"); - auto [should_continue, return_value] = ProcessNextMessage(); + auto [should_continue, return_value] = ProcessNextMessage(transaction); if (!should_continue) { break; } GNS_LOG_INFO("Processing Next Message Successful ({:#x})", return_value); - statusRoutine(return_value, ""); + statusRoutine(return_value, "", transaction); } catch (const std::exception& e) { GNS_LOG_ERROR("Error while processing message: {}", e.what()); - statusRoutine(-1, e.what()); + statusRoutine(-1, e.what(), transaction); } } diff --git a/src/linux/init/GnsEngine.h b/src/linux/init/GnsEngine.h index c6ea91576..39119ead9 100644 --- a/src/linux/init/GnsEngine.h +++ b/src/linux/init/GnsEngine.h @@ -21,10 +21,11 @@ class GnsEngine std::optional AdapterId; }; - using NotificationRoutine = std::function()>; - using StatusRoutine = std::function; + using NotificationRoutine = std::function(wsl::shared::Transaction&)>; + using StatusRoutine = std::function; GnsEngine( + wsl::shared::SocketChannel& channel, const NotificationRoutine& notificationRoutine, const StatusRoutine& statusRoutine, NetworkManager& manager, @@ -36,7 +37,7 @@ class GnsEngine void run(); private: - std::tuple ProcessNextMessage(); + std::tuple ProcessNextMessage(wsl::shared::Transaction& transaction); void ProcessNotification(const nlohmann::json& payload, Interface& interface); @@ -69,6 +70,7 @@ class GnsEngine void ProcessNotificationImpl( Interface& interface, const nlohmann::json& payload, void (GnsEngine::*routine)(Interface&, const T&, wsl::shared::hns::ModifyRequestType)); + wsl::shared::SocketChannel& channel; const NotificationRoutine& notificationRoutine; const StatusRoutine& statusRoutine; NetworkManager& manager; diff --git a/src/linux/init/GnsPortTracker.cpp b/src/linux/init/GnsPortTracker.cpp index 669582a14..92e4beff6 100644 --- a/src/linux/init/GnsPortTracker.cpp +++ b/src/linux/init/GnsPortTracker.cpp @@ -73,7 +73,6 @@ void GnsPortTracker::Run() // for port deallocation std::thread{std::bind(&GnsPortTracker::RunPortRefresh, this)}.detach(); - std::thread{std::bind(&GnsPortTracker::RunDeferredResolve, this)}.detach(); auto future = std::make_optional(m_allocatedPortsRefresh.get_future()); std::optional refreshResult; @@ -121,13 +120,28 @@ void GnsPortTracker::Run() { try { - std::lock_guard lock(m_deferredMutex); - m_deferredQueue.push_back(std::move(bindCall->PortZeroBind.value())); - m_deferredCv.notify_one(); + auto allocation = ResolvePortZeroBind(std::move(bindCall->PortZeroBind.value())); + if (allocation.has_value()) + { + const auto portResult = HandleRequest(allocation.value()); + if (portResult == 0) + { + TrackPort(std::move(allocation.value())); + } + else + { + GNS_LOG_ERROR( + "Failed to register resolved port-0 bind: family ({}) port ({}) protocol ({}), error {}", + allocation->Family, + allocation->Port, + allocation->Protocol, + portResult); + } + } } catch (const std::exception& e) { - GNS_LOG_ERROR("Failed to queue port-0 bind for deferred resolution, {}", e.what()); + GNS_LOG_ERROR("Failed to resolve port-0 bind, {}", e.what()); } } } @@ -148,30 +162,6 @@ void GnsPortTracker::Run() } } - // Process any port-0 binds that the background thread has resolved. - std::deque resolved; - { - std::lock_guard lock(m_resolvedMutex); - resolved.swap(m_resolvedQueue); - } - for (auto& allocation : resolved) - { - const auto result = HandleRequest(allocation); - if (result == 0) - { - TrackPort(std::move(allocation)); - } - else - { - GNS_LOG_ERROR( - "Failed to register resolved port-0 bind: family ({}) port ({}) protocol ({}), error {}", - allocation.Family, - allocation.Port, - allocation.Protocol, - result); - } - } - // Only look at bound ports if there's something to deallocate to avoid wasting cycles if (refreshResult.has_value()) { @@ -539,43 +529,16 @@ catch (const std::exception& e) GNS_LOG_ERROR("Failed to track port allocation, {}", e.what()); } -void GnsPortTracker::RunDeferredResolve() -{ - UtilSetThreadName("GnsPortZero"); - - for (;;) - { - DeferredPortLookup lookup{0, {}, 0}; - { - std::unique_lock lock(m_deferredMutex); - m_deferredCv.wait(lock, [&] { return !m_deferredQueue.empty(); }); - lookup = std::move(m_deferredQueue.front()); - m_deferredQueue.pop_front(); - } - - const auto pid = lookup.Pid; - try - { - ResolvePortZeroBind(std::move(lookup)); - } - catch (const std::exception& e) - { - GNS_LOG_ERROR("Failed to resolve port-0 bind for pid {}, {}", pid, e.what()); - } - } -} - -void GnsPortTracker::ResolvePortZeroBind(DeferredPortLookup lookup) +std::optional GnsPortTracker::ResolvePortZeroBind(DeferredPortLookup lookup) { // The socket fd was already duplicated (via pidfd_getfd) while the target process // was stopped by seccomp, so it remains valid even if the process has closed or // reused the original fd number. - // The bind() syscall is being completed asynchronously on the seccomp dispatcher - // thread after CompleteRequest() unblocks it. Poll getsockname() briefly until - // the kernel assigns a port. + // The bind() syscall has been completed (CompleteRequest() already unblocked the + // caller). Poll getsockname() briefly until the kernel assigns a port. constexpr int maxRetries = 25; - constexpr auto retryDelay = std::chrono::milliseconds(100); + constexpr auto retryDelay = std::chrono::milliseconds(10); in_port_t port = 0; in6_addr address = {}; @@ -593,7 +556,7 @@ void GnsPortTracker::ResolvePortZeroBind(DeferredPortLookup lookup) if (getsockname(lookup.DuplicatedSocketFd.get(), reinterpret_cast(&storage), &addrLen) != 0) { GNS_LOG_ERROR("Port-0 bind: getsockname failed for pid {} (errno {})", lookup.Pid, errno); - return; + return {}; } resolvedFamily = static_cast(storage.ss_family); @@ -613,7 +576,7 @@ void GnsPortTracker::ResolvePortZeroBind(DeferredPortLookup lookup) else { GNS_LOG_ERROR("Port-0 bind: unexpected address family ({}) for pid {}", resolvedFamily, lookup.Pid); - return; + return {}; } if (port != 0) @@ -625,16 +588,12 @@ void GnsPortTracker::ResolvePortZeroBind(DeferredPortLookup lookup) if (port == 0) { GNS_LOG_ERROR("Port-0 bind: kernel did not assign a port for pid {} after retries", lookup.Pid); - return; + return {}; } - PortAllocation allocation(port, resolvedFamily, lookup.Protocol, address); GNS_LOG_INFO( "Port-0 bind resolved: family ({}) port ({}) protocol ({}) for pid {}", resolvedFamily, port, lookup.Protocol, lookup.Pid); - { - std::lock_guard lock(m_resolvedMutex); - m_resolvedQueue.push_back(std::move(allocation)); - } + return PortAllocation(port, resolvedFamily, lookup.Protocol, address); } std::ostream& operator<<(std::ostream& out, const GnsPortTracker::PortAllocation& entry) diff --git a/src/linux/init/GnsPortTracker.h b/src/linux/init/GnsPortTracker.h index a3f2945a1..f15d7c50b 100644 --- a/src/linux/init/GnsPortTracker.h +++ b/src/linux/init/GnsPortTracker.h @@ -1,9 +1,7 @@ // Copyright (C) Microsoft Corporation. All rights reserved. #pragma once -#include #include -#include #include #include #include @@ -146,9 +144,7 @@ class GnsPortTracker static wil::unique_fd DuplicateSocketFd(pid_t Pid, int SocketFd); - void ResolvePortZeroBind(DeferredPortLookup lookup); - - void RunDeferredResolve(); + std::optional ResolvePortZeroBind(DeferredPortLookup lookup); void TrackPort(PortAllocation allocation); @@ -163,15 +159,6 @@ class GnsPortTracker std::shared_ptr m_seccompDispatcher; std::string m_networkNamespace; - - std::mutex m_deferredMutex; - std::condition_variable m_deferredCv; - std::deque m_deferredQueue; - - // Resolved port-0 allocations posted by the background RunDeferredResolve thread - // for the main Run() loop to process (keeps SocketChannel access single-threaded). - std::mutex m_resolvedMutex; - std::deque m_resolvedQueue; }; std::ostream& operator<<(std::ostream& out, const GnsPortTracker::PortAllocation& portAllocation); diff --git a/src/linux/init/SecCompDispatcher.cpp b/src/linux/init/SecCompDispatcher.cpp index 98a81c3bd..59ce6f63e 100644 --- a/src/linux/init/SecCompDispatcher.cpp +++ b/src/linux/init/SecCompDispatcher.cpp @@ -104,8 +104,8 @@ void SecCompDispatcher::Run() } int result = 0; GNS_LOG_INFO( - "Notified for arch {:X} syscall {} with id {}lu for pid {} with args ({}lX, {}lX, {}lX, {}lX, {}lX, " - "{}lX)", + "Notified for arch {:X} syscall {} with id {} for pid {} with args ({:X}, {:X}, {:X}, {:X}, {:X}, " + "{:X})", callInfo->data.arch, callInfo->data.nr, callInfo->id, @@ -140,14 +140,14 @@ void SecCompDispatcher::Run() resultInfo->val = 0; resultInfo->flags = result == 0 ? SECCOMP_USER_NOTIF_FLAG_CONTINUE : 0; - GNS_LOG_INFO("Responding to notification with id {}lu for pid {}, result {}", callInfo->id, callInfo->pid, result); + GNS_LOG_INFO("Responding to notification with id {} for pid {}, result {}", callInfo->id, callInfo->pid, result); try { Syscall(ioctl, m_notifyFd.get(), SECCOMP_IOCTL_NOTIF_SEND, resultInfo); } catch (std::exception& e) { - GNS_LOG_ERROR("Failed to respond to notification with id {}lu for pid {}, {}", callInfo->id, callInfo->pid, e.what()); + GNS_LOG_ERROR("Failed to respond to notification with id {} for pid {}, {}", callInfo->id, callInfo->pid, e.what()); } } } @@ -211,7 +211,7 @@ std::optional> SecCompDispatcher::ReadProcessMemory(uint6 } catch (std::exception& e) { - GNS_LOG_ERROR("Failed to read process memory for pid {}, cookie {}u, {}", Pid, Cookie, e.what()); + GNS_LOG_ERROR("Failed to read process memory for pid {}, cookie {}, {}", Pid, Cookie, e.what()); return std::nullopt; } } diff --git a/src/linux/init/binfmt.cpp b/src/linux/init/binfmt.cpp index 0bc10e289..496a2d302 100644 --- a/src/linux/init/binfmt.cpp +++ b/src/linux/init/binfmt.cpp @@ -174,7 +174,8 @@ try // Send the create process message to the interop server. // - channel.SendMessage(Span); + auto transaction = channel.StartTransaction(); + transaction.Send(Span); // // Accept connections from the interop server. diff --git a/src/linux/init/config.cpp b/src/linux/init/config.cpp index b4a554b1c..7e621e98b 100644 --- a/src/linux/init/config.cpp +++ b/src/linux/init/config.cpp @@ -334,7 +334,7 @@ try CATCH_LOG() void ConfigHandleInteropMessage( - wsl::shared::SocketChannel& ResponseChannel, + wsl::shared::Transaction& Transaction, wsl::shared::SocketChannel& InteropChannel, bool Elevated, gsl::span Message, @@ -350,7 +350,7 @@ Routine Description: Arguments: - ResponseChannel - Supplies channel used to send responses. + Transaction - Supplies transaction used to send responses. InteropChannel - Supplies a channel to the host to be used for create process requests. @@ -381,7 +381,7 @@ try case LxInitMessageQueryDrvfsElevated: { - ResponseChannel.SendResultMessage(Elevated); + Transaction.SendResultMessage(Elevated); break; } @@ -397,7 +397,7 @@ try auto Value = UtilGetEnvironmentVariable(Query->Buffer); wsl::shared::MessageWriter Response(LxInitMessageQueryEnvironmentVariable); Response.WriteString(Value); - ResponseChannel.SendMessage(Response.Span()); + Transaction.Send(Response.Span()); } break; @@ -405,7 +405,7 @@ try case LxInitMessageQueryFeatureFlags: { assert(Config.FeatureFlags.has_value()); - ResponseChannel.SendResultMessage(Config.FeatureFlags.value()); + Transaction.SendResultMessage(Config.FeatureFlags.value()); break; } @@ -419,7 +419,7 @@ try } bool success = false; - auto sendResponse = wil::scope_exit([&]() { ResponseChannel.SendResultMessage(success); }); + auto sendResponse = wil::scope_exit([&]() { Transaction.SendResultMessage(success); }); if (!Config.BootInit || Config.InitPid.value_or(0) != getpid()) { @@ -435,7 +435,7 @@ try case LxInitMessageQueryNetworkingMode: assert(Config.NetworkingMode.has_value()); - ResponseChannel.SendResultMessage(static_cast(Config.NetworkingMode.value())); + Transaction.SendResultMessage(static_cast(Config.NetworkingMode.value())); break; case LxInitMessageQueryVmId: @@ -446,7 +446,7 @@ try Response.WriteString(Config.VmId.value()); } - ResponseChannel.SendMessage(Response.Span()); + Transaction.Send(Response.Span()); break; } @@ -618,7 +618,7 @@ try } CATCH_LOG() -int ConfigInitializeInstance(wsl::shared::SocketChannel& Channel, gsl::span Buffer, wsl::linux::WslDistributionConfig& Config) +int ConfigInitializeInstance(const std::function&)>& SendResponse, gsl::span Buffer, wsl::linux::WslDistributionConfig& Config) /*++ @@ -632,7 +632,7 @@ Routine Description: Arguments: - MessageFd - Supplies a file descriptor to send the response message. + SendResponse - Supplies a function to send the response message. Buffer - Supplies the message buffer. @@ -923,7 +923,7 @@ try Response.WriteString(Response->VersionIndex, Version->c_str()); } - Channel.SendMessage(Response.Span()); + SendResponse(Response.Span()); // // Accept the interop connection. @@ -973,13 +973,14 @@ try continue; } - auto [Message, Span] = ClientChannel.ReceiveMessageOrClosed(); + auto transaction = ClientChannel.ReceiveTransaction(); + auto [Message, Span] = transaction.ReceiveOrClosed(); if (Message == nullptr) { continue; } - ConfigHandleInteropMessage(ClientChannel, InteropChannel, Elevated, Span, Message, Config); + ConfigHandleInteropMessage(transaction, InteropChannel, Elevated, Span, Message, Config); } }); @@ -2186,7 +2187,7 @@ Return Value: return Result; } -int ConfigRemountDrvFs(gsl::span Buffer, wsl::shared::SocketChannel& Channel, const wsl::linux::WslDistributionConfig& Config) +int ConfigRemountDrvFs(gsl::span Buffer, wsl::shared::Transaction& Transaction, const wsl::linux::WslDistributionConfig& Config) /*++ @@ -2207,7 +2208,7 @@ Return Value: --*/ { - Channel.SendResultMessage(ConfigRemountDrvFsImpl(Buffer, Config)); + Transaction.SendResultMessage(ConfigRemountDrvFsImpl(Buffer, Config)); return 0; } @@ -2246,7 +2247,7 @@ try const auto* Message = gslhelpers::try_get_struct(Buffer); if (!Message) { - LOG_ERROR("Unexpected sizeof for LX_INIT_MOUNT_DRVFS: {}u", Buffer.size()); + LOG_ERROR("Unexpected sizeof for LX_INIT_MOUNT_DRVFS: {}", Buffer.size()); return -1; } diff --git a/src/linux/init/config.h b/src/linux/init/config.h index bec5d71c2..771bda245 100644 --- a/src/linux/init/config.h +++ b/src/linux/init/config.h @@ -20,6 +20,7 @@ Module Name: #include #include #include +#include #include "SocketChannel.h" #include "WslDistributionConfig.h" @@ -399,7 +400,7 @@ std::set> ConfigGetMountedDrvFsVolumes(void std::vector> ConfigGetWslgEnvironmentVariables(const wsl::linux::WslDistributionConfig& Config); void ConfigHandleInteropMessage( - wsl::shared::SocketChannel& ResponseChannel, + wsl::shared::Transaction& Transaction, wsl::shared::SocketChannel& InteropChannel, bool Elevated, gsl::span Message, @@ -408,7 +409,7 @@ void ConfigHandleInteropMessage( void ConfigInitializeCgroups(wsl::linux::WslDistributionConfig& Config); -int ConfigInitializeInstance(wsl::shared::SocketChannel& Channel, gsl::span Buffer, wsl::linux::WslDistributionConfig& Config); +int ConfigInitializeInstance(const std::function&)>& SendResponse, gsl::span Buffer, wsl::linux::WslDistributionConfig& Config); void ConfigMountDrvFsVolumes(unsigned int DrvFsVolumes, uid_t OwnerUid, std::optional Admin, const wsl::linux::WslDistributionConfig& Config); @@ -420,7 +421,7 @@ int ConfigRegisterBinfmtInterpreter(void); int ConfigSetMountNamespace(bool Elevated); -int ConfigRemountDrvFs(gsl::span Buffer, wsl::shared::SocketChannel& Channel, const wsl::linux::WslDistributionConfig& Config); +int ConfigRemountDrvFs(gsl::span Buffer, wsl::shared::Transaction& Transaction, const wsl::linux::WslDistributionConfig& Config); int ConfigRemountDrvFsImpl(gsl::span Buffer, const wsl::linux::WslDistributionConfig& Config); diff --git a/src/linux/init/drvfs.cpp b/src/linux/init/drvfs.cpp index c36ae7e81..128157e57 100644 --- a/src/linux/init/drvfs.cpp +++ b/src/linux/init/drvfs.cpp @@ -209,8 +209,9 @@ Return Value: QueryPortMessage.MessageType = LxInitMessageQueryDrvfsElevated; QueryPortMessage.MessageSize = sizeof(QueryPortMessage); - channel.SendMessage(QueryPortMessage); - return channel.ReceiveMessage>().Result; + auto transaction = channel.StartTransaction(); + transaction.Send(QueryPortMessage); + return transaction.Receive>().Result; } int MountFilesystem(const char* FsType, const char* Source, const char* Target, const char* Options, int* ExitCode) diff --git a/src/linux/init/init.cpp b/src/linux/init/init.cpp index 479b0992f..d1734caa1 100644 --- a/src/linux/init/init.cpp +++ b/src/linux/init/init.cpp @@ -116,10 +116,15 @@ int InitConnectToServer(int LxBusFd, bool WaitForServer); int InitCreateProcessUtilityVm( gsl::span Message, const LX_INIT_CREATE_PROCESS_UTILITY_VM& Header, - wsl::shared::SocketChannel& MessageFd, + wsl::shared::Transaction& Transaction, const wsl::linux::WslDistributionConfig& Config); -int InitCreateSessionLeader(gsl::span Buffer, wsl::shared::SocketChannel& Channel, int LxBusFd, wsl::linux::WslDistributionConfig& Config); +int InitCreateSessionLeader( + gsl::span Buffer, + wsl::shared::SocketChannel& Channel, + const std::function& SendResponse, + int LxBusFd, + wsl::linux::WslDistributionConfig& Config); void InitEntry(int Argc, char* Argv[]); @@ -127,7 +132,7 @@ void InitEntryWsl(wsl::linux::WslDistributionConfig& Config); void InitEntryUtilityVm(wsl::linux::WslDistributionConfig& Config); -void InitTerminateInstance(gsl::span Buffer, wsl::shared::SocketChannel& Channel, wsl::linux::WslDistributionConfig& Config); +void InitTerminateInstance(gsl::span Buffer, const std::function& SendResult, wsl::linux::WslDistributionConfig& Config); void InitTerminateInstanceInternal(const wsl::linux::WslDistributionConfig& Config); @@ -1111,7 +1116,12 @@ Return Value: return 0; } -int InitCreateSessionLeader(gsl::span Buffer, wsl::shared::SocketChannel& Channel, int LxBusFd, wsl::linux::WslDistributionConfig& Config) +int InitCreateSessionLeader( + gsl::span Buffer, + wsl::shared::SocketChannel& Channel, + const std::function& SendResponse, + int LxBusFd, + wsl::linux::WslDistributionConfig& Config) /*++ @@ -1228,7 +1238,7 @@ try Response.Header.MessageType = LxInitMessageCreateSessionResponse; Response.Header.MessageSize = sizeof(Response); Response.Port = SocketAddress.svm_port; - Channel.SendMessage(Response); + SendResponse(Response); if (!ListenSocket) { @@ -1329,7 +1339,7 @@ Return Value: int InitCreateProcessUtilityVm( gsl::span Span, const LX_INIT_CREATE_PROCESS_UTILITY_VM& CreateProcess, - wsl::shared::SocketChannel& Channel, + wsl::shared::Transaction& Transaction, const wsl::linux::WslDistributionConfig& Config) /*++ @@ -1414,7 +1424,7 @@ Return Value: // Tell the service which sockets ports to connect to. // - Channel.SendResultMessage(SocketAddress.svm_port); + Transaction.SendResultMessage(SocketAddress.svm_port); // // Exit if creating the listening socket failed. @@ -1978,13 +1988,14 @@ Return Value: continue; } - auto [Header, Span] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [Header, Span] = transaction.ReceiveOrClosed(); if (Header != nullptr) { try { ConfigHandleInteropMessage( - channel, ControlChannel, WI_IsFlagSet(CreateProcess.Common.Flags, LxInitCreateProcessFlagsElevated), Span, Header, Config); + transaction, ControlChannel, WI_IsFlagSet(CreateProcess.Common.Flags, LxInitCreateProcessFlagsElevated), Span, Header, Config); } CATCH_LOG(); } @@ -2426,6 +2437,16 @@ Return Value: FATAL_ERROR("signalfd failed {}", errno); } + // Handle the case where the child already exited before signalfd was set up. + int Status{}; + auto WaitResult = waitpid(distroInitPid.value(), &Status, WNOHANG); + if (WaitResult > 0 || (WaitResult < 0 && errno == ECHILD)) + { + LOG_ERROR("Init has exited. Terminating distribution"); + InitTerminateInstanceInternal(Config); + return; + } + PollDescriptors.resize(2); PollDescriptors[1].fd = SignalFd.get(); PollDescriptors[1].events = POLLIN; @@ -2445,7 +2466,8 @@ Return Value: } else if (PollDescriptors[0].revents & POLLIN) { - auto [Header, Span] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [Header, Span] = transaction.ReceiveOrClosed(); if (Header == nullptr) { break; @@ -2454,16 +2476,23 @@ Return Value: switch (Header->MessageType) { case LxInitMessageCreateSession: - if (InitCreateSessionLeader(Span, channel, -1, Config) < 0) + { + auto SendResponse = [&](LX_INIT_CREATE_SESSION_RESPONSE& response) { transaction.Send(response); }; + if (InitCreateSessionLeader(Span, channel, SendResponse, -1, Config) < 0) { FATAL_ERROR("InitCreateSessionLeader failed"); } - - break; + } + break; case LxInitMessageInitialize: - ConfigInitializeInstance(channel, Span, Config); - break; + { + auto SendResponse = [&](const gsl::span& span) { + transaction.Send(span); + }; + ConfigInitializeInstance(SendResponse, Span, Config); + } + break; case LxInitMessageTimezoneInformation: UpdateTimezone(Span, Config); @@ -2479,15 +2508,18 @@ Return Value: // WaitForBootProcess(Config); - ConfigRemountDrvFs(Span, channel, Config); + ConfigRemountDrvFs(Span, transaction, Config); break; case LxInitMessageTerminateInstance: - InitTerminateInstance(Span, channel, Config); - break; + { + auto SendResult = [&](bool result) { transaction.SendResultMessage(result); }; + InitTerminateInstance(Span, SendResult, Config); + } + break; case LxInitCreateProcess: - ProcessCreateProcessMessage(channel, Span); + ProcessCreateProcessMessage(transaction, Span); break; default: @@ -2602,7 +2634,9 @@ Return Value: switch (Header->MessageType) { case LxInitMessageCreateSession: - if (InitCreateSessionLeader(Message, Channel, LxBusFd.get(), Config) < 0) + { + auto SendResponse = [&](LX_INIT_CREATE_SESSION_RESPONSE& response) { Channel.SendMessage(response); }; + if (InitCreateSessionLeader(Message, Channel, SendResponse, LxBusFd.get(), Config) < 0) { // // If this distro has no children, exit on failure. @@ -2616,24 +2650,32 @@ Return Value: LOG_ERROR("InitCreateSessionLeader failed"); } - - break; + } + break; case LxInitMessageNetworkInformation: ConfigUpdateNetworkInformation(Message, Config); break; case LxInitMessageInitialize: - ConfigInitializeInstance(Channel, Message, Config); - break; + { + auto SendResponse = [&](const gsl::span& span) { + Channel.SendMessage(span); + }; + ConfigInitializeInstance(SendResponse, Message, Config); + } + break; case LxInitMessageTimezoneInformation: UpdateTimezone(Message, Config); break; case LxInitMessageTerminateInstance: - InitTerminateInstance(Message, Channel, Config); - break; + { + auto SendResult = [&](bool result) { Channel.SendResultMessage(result); }; + InitTerminateInstance(Message, SendResult, Config); + } + break; default: FATAL_ERROR("Unexpected message {}", Header->MessageType); @@ -2643,7 +2685,7 @@ Return Value: return; } -void InitTerminateInstance(gsl::span Buffer, wsl::shared::SocketChannel& Channel, wsl::linux::WslDistributionConfig& Config) +void InitTerminateInstance(gsl::span Buffer, const std::function& SendResult, wsl::linux::WslDistributionConfig& Config) /*++ @@ -2655,7 +2697,7 @@ Routine Description: Buffer - Supplies the message buffer. - Channel - Supplies a channel to send the response. + SendResult - Supplies a function to send the response. Config - Supplies the distribution config. @@ -2680,7 +2722,7 @@ try if (!StopPlan9Server(Message->Force, Config)) { - Channel.SendResultMessage(false); + SendResult(false); return; } @@ -3026,7 +3068,8 @@ Return Value: for (;;) { - auto [Message, Span] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [Message, Span] = transaction.ReceiveOrClosed(); if (Message == nullptr) { _exit(0); @@ -3035,7 +3078,7 @@ Return Value: switch (Message->Header.MessageType) { case LxInitMessageCreateProcessUtilityVm: - if (InitCreateProcessUtilityVm(Span, *Message, channel, Config) < 0) + if (InitCreateProcessUtilityVm(Span, *Message, transaction, Config) < 0) { FATAL_ERROR("InitCreateProcessUtilityVm failed"); } @@ -3284,7 +3327,7 @@ unsigned int StartGns(int Argc, char** Argv) if (channel.Socket() == -1) { - readNotification = [&]() -> std::optional { + readNotification = [&](wsl::shared::Transaction&) -> std::optional { std::string content{std::istreambuf_iterator(std::cin), std::istreambuf_iterator()}; if (content.empty()) { @@ -3298,7 +3341,7 @@ unsigned int StartGns(int Argc, char** Argv) return {{AdapterId.has_value() ? LxGnsMessageNotification : LxGnsMessageInterfaceConfiguration, content, AdapterId}}; }; - returnStatus = [&](int Result, const std::string& Error) { + returnStatus = [&](int Result, const std::string& Error, wsl::shared::Transaction&) { GNS_LOG_INFO("Returning LxGnsMessageResult (no output fd) [{} - {}]", Result, Error.c_str()); // exitCode keeps the most recent error in the test path if (Result != 0) @@ -3310,9 +3353,9 @@ unsigned int StartGns(int Argc, char** Argv) } else { - readNotification = [&]() -> std::optional { + readNotification = [&](wsl::shared::Transaction& transaction) -> std::optional { std::vector Buffer; - auto [Message, Span] = channel.ReceiveMessageOrClosed(); + auto [Message, Span] = transaction.ReceiveOrClosed(); if (Message == nullptr) { return {}; @@ -3375,7 +3418,7 @@ unsigned int StartGns(int Argc, char** Argv) } }; - returnStatus = [&](int Result, const std::string& Error) { + returnStatus = [&](int Result, const std::string& Error, wsl::shared::Transaction& transaction) { std::vector Buffer(sizeof(LX_GNS_RESULT) + Error.size() + 1); GNS_LOG_INFO("Returning LxGnsMessageResult [{} - {}]", Result, Error.c_str()); @@ -3387,13 +3430,13 @@ unsigned int StartGns(int Argc, char** Argv) response.WriteString(Error); } - return channel.SendMessage(response.Span()); + return transaction.Send(response.Span()); }; } RoutingTable routingTable(RT_TABLE_MAIN); NetworkManager manager(routingTable); - GnsEngine engine(readNotification, returnStatus, manager, DnsFd, DnsTunnelingIp); + GnsEngine engine(channel, readNotification, returnStatus, manager, DnsFd, DnsTunnelingIp); engine.run(); diff --git a/src/linux/init/localhost.cpp b/src/linux/init/localhost.cpp index 369f6144a..cbd69f3b2 100644 --- a/src/linux/init/localhost.cpp +++ b/src/linux/init/localhost.cpp @@ -127,7 +127,8 @@ try { auto message = SockToRelayMessage(sock); message.Header.MessageType = LxGnsMessagePortListenerRelayStart; - channel.SendMessage(message); + auto transaction = channel.StartTransaction(); + transaction.Send(message); return 0; } @@ -138,7 +139,8 @@ try { auto message = SockToRelayMessage(sock); message.Header.MessageType = LxGnsMessagePortListenerRelayStop; - channel.SendMessage(message); + auto transaction = channel.StartTransaction(); + transaction.Send(message); return 0; } diff --git a/src/linux/init/main.cpp b/src/linux/init/main.cpp index 0370ad291..4c91f0078 100644 --- a/src/linux/init/main.cpp +++ b/src/linux/init/main.cpp @@ -193,7 +193,7 @@ int MountInit(const char* Target); int MountPlan9(const char* Name, const char* Target, bool ReadOnly, std::optional BufferSize = {}); -int ProcessMessage(wsl::shared::SocketChannel& channel, LX_MESSAGE_TYPE Type, gsl::span Buffer, VmConfiguration& Config); +int ProcessMessage(wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, gsl::span Buffer, VmConfiguration& Config); wil::unique_fd RegisterSeccompHook(); @@ -324,7 +324,7 @@ try sched_param Parameter{}; Parameter.sched_priority = 0; - THROW_LAST_ERROR_IF(pthread_setschedparam(pthread_self(), SCHED_IDLE, &Parameter) < 0); + THROW_LAST_ERROR_IF(pthread_setschedparam(pthread_self(), SCHED_IDLE, &Parameter) != 0); // // Periodically check if the machine is idle by querying procfs for CPU usage. @@ -471,7 +471,7 @@ Return Value: return {}; } - struct sockaddr_nl Address; + struct sockaddr_nl Address{}; Address.nl_family = AF_NETLINK; if (bind(Fd.get(), (struct sockaddr*)&Address, sizeof(Address)) < 0) { @@ -587,7 +587,7 @@ Return Value: std::string content = wsl::shared::string::ReadFile(std::format("/sys/block/{}/dev", BlockDeviceName).c_str()); auto separator = content.find(':'); - if (separator == 0 || separator - 1 >= content.size() || separator == std::string::npos) + if (separator == std::string::npos || separator == 0 || separator + 1 == content.size()) { LOG_ERROR("Failed to parse device number '{}' for device '{}'", content.c_str(), BlockDeviceName.c_str()); THROW_ERRNO(EINVAL); @@ -2801,7 +2801,7 @@ void ProcessImportExportMessage(gsl::span Buffer, wsl::shared::Socket } } -int ProcessMountFolderMessage(wsl::shared::SocketChannel& Channel, gsl::span Buffer) +int ProcessMountFolderMessage(wsl::shared::Transaction& Transaction, gsl::span Buffer) /*++ @@ -2837,7 +2837,7 @@ Return Value: } int Result = MountPlan9(Name, Target, Message->ReadOnly); - Channel.SendResultMessage(Result); + Transaction.SendResultMessage(Result); return 0; } @@ -3156,7 +3156,7 @@ try } CATCH_RETURN_ERRNO(); -int ProcessMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, gsl::span Buffer, VmConfiguration& Config) +int ProcessMessage(wsl::shared::Transaction& Transaction, LX_MESSAGE_TYPE Type, gsl::span Buffer, VmConfiguration& Config) /*++ @@ -3166,9 +3166,7 @@ Routine Description: Arguments: - MessageFd - Supplies a file descriptor to the socket on which the message was - received. This is used for operations that require responses, for example a - VHD eject request. + Transaction - Supplies the transaction for replying to the message. Buffer - Supplies the message. @@ -3252,7 +3250,7 @@ try return -1; } - Channel.SendResultMessage(EjectScsi(EjectMessage->Lun)); + Transaction.SendResultMessage(EjectScsi(EjectMessage->Lun)); return 0; } @@ -3488,10 +3486,10 @@ try return 0; case LxMiniInitMountFolder: - return ProcessMountFolderMessage(Channel, Buffer); + return ProcessMountFolderMessage(Transaction, Buffer); case LxInitCreateProcess: - return ProcessCreateProcessMessage(Channel, Buffer); + return ProcessCreateProcessMessage(Transaction, Buffer); case LxMiniInitMessageWaitForPmemDevice: { @@ -4180,13 +4178,14 @@ int main(int Argc, char* Argv[]) } else if (PollDescriptors[0].revents & POLLIN) { - auto [Message, Range] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [Message, Range] = transaction.ReceiveOrClosed(); if (Message == nullptr) { break; // Socket was closed, exit } - Result = ProcessMessage(channel, Message->MessageType, Range, Config); + Result = ProcessMessage(transaction, Message->MessageType, Range, Config); if (Result < 0) { goto ErrorExit; diff --git a/src/linux/init/plan9.cpp b/src/linux/init/plan9.cpp index 1cc5cf3c0..e7c7cc936 100644 --- a/src/linux/init/plan9.cpp +++ b/src/linux/init/plan9.cpp @@ -157,13 +157,14 @@ try std::vector Buffer; for (;;) { - auto [Message, _] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [Message, _] = transaction.ReceiveOrClosed(); if (Message == nullptr) { _exit(0); } - channel.SendResultMessage(StopPlan9Server(fileSystem, Message->Force)); + transaction.SendResultMessage(StopPlan9Server(fileSystem, Message->Force)); } } CATCH_LOG(); @@ -183,7 +184,7 @@ void RunPlan9Server(const char* socketPath, const char* logFile, int logLevel, b limit.rlim_cur = limit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &limit) < 0) { - LOG_ERROR("setrlimit(RLIMIT_NOFILE, {}lu, {}lu) failed {}", limit.rlim_cur, limit.rlim_max, errno); + LOG_ERROR("setrlimit(RLIMIT_NOFILE, {}, {}) failed {}", limit.rlim_cur, limit.rlim_max, errno); } // Open the root. diff --git a/src/linux/init/util.cpp b/src/linux/init/util.cpp index d24c812c3..2c6f3ddae 100644 --- a/src/linux/init/util.cpp +++ b/src/linux/init/util.cpp @@ -172,6 +172,7 @@ Return Value: if (!InteropConnection) { LOG_ERROR("accept4 failed {}", errno); + return {}; } timeval Timeout{}; @@ -786,10 +787,6 @@ Return Value: if (Output) { (*Output) += Buffer.data(); - if (Result < 0) - { - goto ErrorExit; - } } else { @@ -1112,13 +1109,14 @@ try wsl::shared::MessageWriter Message(LxInitMessageQueryEnvironmentVariable); Message.WriteString(Name); - channel.SendMessage(Message.Span()); + auto transaction = channel.StartTransaction(); + transaction.Send(Message.Span()); // // Read a response, this will contain the environment variable value if it exists. // - Value = channel.ReceiveMessage().Buffer; + Value = transaction.Receive().Buffer; // // Set the environment variable for future queries. @@ -1197,8 +1195,9 @@ Return Value: Message.MessageType = LxInitMessageQueryFeatureFlags; Message.MessageSize = sizeof(Message); - channel.SendMessage(Message); - FeatureFlags = channel.ReceiveMessage>().Result; + auto transaction = channel.StartTransaction(); + transaction.Send(Message); + FeatureFlags = transaction.Receive>().Result; } UtilSetFeatureFlags(FeatureFlags, FeatureFlagEnv == nullptr); @@ -1266,9 +1265,10 @@ try Message.MessageType = LxInitMessageQueryNetworkingMode; Message.MessageSize = sizeof(Message); - channel.SendMessage(Message); + auto transaction = channel.StartTransaction(); + transaction.Send(Message); - const auto& response = channel.ReceiveMessage>(); + const auto& response = transaction.Receive>(); auto NetworkingMode = static_cast(response.Result); THROW_ERRNO_IF(EINVAL, NetworkingMode < LxMiniInitNetworkingModeNone || NetworkingMode > LxMiniInitNetworkingModeVirtioProxy); @@ -1360,9 +1360,10 @@ try THROW_LAST_ERROR_IF(channel.Socket() < 0); wsl::shared::MessageWriter Message(LxInitMessageQueryVmId); - channel.SendMessage(Message.Span()); + auto transaction = channel.StartTransaction(); + transaction.Send(Message.Span()); - return channel.ReceiveMessage().Buffer; + return transaction.Receive().Buffer; } catch (...) { @@ -3380,7 +3381,7 @@ Return Value: return 0; } -int ProcessCreateProcessMessage(wsl::shared::SocketChannel& channel, gsl::span Buffer) +int ProcessCreateProcessMessage(wsl::shared::Transaction& Transaction, gsl::span Buffer) { auto* Message = gslhelpers::try_get_struct(Buffer); if (!Message) @@ -3389,7 +3390,7 @@ int ProcessCreateProcessMessage(wsl::shared::SocketChannel& channel, gsl::span(Result); }; + auto sendResult = [&](unsigned long Result) { Transaction.SendResultMessage(Result); }; sockaddr_vm SocketAddress{}; wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, false)}; diff --git a/src/linux/init/util.h b/src/linux/init/util.h index 1512d00e8..1d287edd0 100644 --- a/src/linux/init/util.h +++ b/src/linux/init/util.h @@ -35,7 +35,8 @@ Module Name: namespace wsl::shared { class SocketChannel; -} +class Transaction; +} // namespace wsl::shared namespace wsl::linux { struct WslDistributionConfig; @@ -314,4 +315,4 @@ uint16_t UtilWinAfToLinuxAf(uint16_t AddressFamily); int WriteToFile(const char* Path, const char* Content, int permissions = 0644); -int ProcessCreateProcessMessage(wsl::shared::SocketChannel& channel, gsl::span Buffer); \ No newline at end of file +int ProcessCreateProcessMessage(wsl::shared::Transaction& Transaction, gsl::span Buffer); \ No newline at end of file diff --git a/src/linux/init/wslinfo.cpp b/src/linux/init/wslinfo.cpp index 09e092dd1..2c666d40f 100644 --- a/src/linux/init/wslinfo.cpp +++ b/src/linux/init/wslinfo.cpp @@ -8,7 +8,7 @@ Module Name: Abstract: - This file wslpath function definitions. + This file contains wslinfo function definitions. --*/ diff --git a/src/linux/mountutil/mountutil.c b/src/linux/mountutil/mountutil.c index b23e7766e..ee05c26b5 100644 --- a/src/linux/mountutil/mountutil.c +++ b/src/linux/mountutil/mountutil.c @@ -232,6 +232,7 @@ int MountParseMountInfoLine(char* line, PMOUNT_ENTRY entry) { goto ParseMountInfoLineEnd; } + break; case MountFieldRoot: entry->Root = current; diff --git a/src/linux/netlinkutil/Interface.cpp b/src/linux/netlinkutil/Interface.cpp index 5efe0fa64..9a71d693c 100644 --- a/src/linux/netlinkutil/Interface.cpp +++ b/src/linux/netlinkutil/Interface.cpp @@ -356,7 +356,7 @@ void Interface::SetActiveChild(const Interface& child_interface) void Interface::CreateTunTapAdapter(const std::string& name, bool TunAdapter) { wil::unique_fd fd; - if (name.size() > IFNAMSIZ) + if (name.size() >= IFNAMSIZ) { throw RuntimeErrorWithSourceLocation("Tun adapter name exceeds IFNAMSIZ"); } @@ -644,7 +644,7 @@ void Interface::EnableNetworkSetting(const char* settingName, int addressFamily) wil::unique_fd fd(Syscall(open, settingFilePath.c_str(), (O_WRONLY | O_CLOEXEC))); - Syscall(write, fd.get(), c_value1, sizeof(c_value1)); + Syscall(write, fd.get(), c_value1, sizeof(c_value1) - 1); } void Interface::DisableNetworkSetting(const char* settingName, int addressFamily) @@ -654,7 +654,7 @@ void Interface::DisableNetworkSetting(const char* settingName, int addressFamily wil::unique_fd fd(Syscall(open, settingFilePath.c_str(), (O_WRONLY | O_CLOEXEC))); - Syscall(write, fd.get(), c_value0, sizeof(c_value0)); + Syscall(write, fd.get(), c_value0, sizeof(c_value0) - 1); } void Interface::ResetIpv6State() diff --git a/src/linux/netlinkutil/NetlinkMessage.hxx b/src/linux/netlinkutil/NetlinkMessage.hxx index 3f865f26a..e1966ac9b 100644 --- a/src/linux/netlinkutil/NetlinkMessage.hxx +++ b/src/linux/netlinkutil/NetlinkMessage.hxx @@ -70,7 +70,7 @@ std::vector NetlinkMessage::Attributes(int type) co std::format( "Attribute at offset {}: attempted to access beyond attribute offset ({} > {})", (reinterpret_cast(e) - &*m_responseBegin), - sizeof(TMessage), + sizeof(TAttribute), e->rta_len)); } diff --git a/src/linux/plan9/p9file.cpp b/src/linux/plan9/p9file.cpp index ee5df801b..f318c216f 100644 --- a/src/linux/plan9/p9file.cpp +++ b/src/linux/plan9/p9file.cpp @@ -1060,7 +1060,7 @@ LX_INT File::Access(AccessFlags flags) } std::string parentPath; - const int index = name.find_last_of('/'); + const auto index = name.find_last_of('/'); if (index != std::string::npos) { parentPath = name.substr(0, index); diff --git a/src/linux/plan9/p9file.h b/src/linux/plan9/p9file.h index d82b19c4d..53318db8c 100644 --- a/src/linux/plan9/p9file.h +++ b/src/linux/plan9/p9file.h @@ -33,7 +33,7 @@ struct Root final : public IRoot std::vector buffer(bufsize); passwd pwd{}; passwd* result = nullptr; - if (getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &result) < 0 || result == nullptr) + if (getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &result) != 0 || result == nullptr) { Plan9TraceLoggingProvider::LogMessage(std::format("getpwuid_r failed for uid: {}, errno={}", uid, errno)); return; diff --git a/src/linux/plan9/p9io.cpp b/src/linux/plan9/p9io.cpp index be5d92cfc..702c2e12a 100644 --- a/src/linux/plan9/p9io.cpp +++ b/src/linux/plan9/p9io.cpp @@ -24,14 +24,14 @@ CoroutineIoIssuer::CoroutineIoIssuer(int fd) : m_FileDescriptor(fd) void CoroutineIoIssuer::Callback(sigval value) { const auto operation = static_cast(value.sival_ptr); - auto bytesTransferred = aio_return(&operation->ControlBlock); - int error = 0; - if (bytesTransferred < 0) + auto error = aio_error(&operation->ControlBlock); + if (error == EINPROGRESS) { - error = aio_error(&operation->ControlBlock); + return; } + auto bytesTransferred = aio_return(&operation->ControlBlock); - operation->Result = {error, static_cast(bytesTransferred)}; + operation->Result = {-error, error == 0 ? static_cast(bytesTransferred) : 0}; if (!operation->DoneOrCoroutine.exchange(true)) { return; @@ -55,7 +55,7 @@ bool CoroutineIoIssuer::PreIssue(CoroutineIoOperation& operation, CancelToken& t } // The operation has already been cancelled. Don't even issue the IO. - operation.Result = {ECANCELED, 0}; + operation.Result = {-ECANCELED, 0}; operation.DoneOrCoroutine = true; return false; } @@ -286,7 +286,7 @@ Task WriteAsync(CoroutineIoIssuer& file, std::uint64_t offset, gsl::sp cb.aio_offset = offset; if (aio_write(&cb) < 0) { - return {errno, 0}; + return {-errno, 0}; } return {}; diff --git a/src/linux/plan9/p9readdir.cpp b/src/linux/plan9/p9readdir.cpp index a75abcf4c..b7ae57831 100644 --- a/src/linux/plan9/p9readdir.cpp +++ b/src/linux/plan9/p9readdir.cpp @@ -27,7 +27,7 @@ struct dirent* DirectoryEnumerator::Next() if (result == nullptr) { // If errno is still 0, it means EOF is reached which is not an error. - THROW_LAST_ERROR_IF(errno != 0) + THROW_LAST_ERROR_IF(errno != 0); } else { diff --git a/src/linux/plan9/p9util.cpp b/src/linux/plan9/p9util.cpp index c2a513b13..bcb8d2c2c 100644 --- a/src/linux/plan9/p9util.cpp +++ b/src/linux/plan9/p9util.cpp @@ -162,7 +162,7 @@ gid_t GetUserGroupId(uid_t uid) for (;;) { buffer.resize(size); - if (getpwuid_r(uid, &pwd, buffer.data(), size, &result) < 0) + if (getpwuid_r(uid, &pwd, buffer.data(), size, &result) != 0) { if (errno != ERANGE) { @@ -198,7 +198,7 @@ gid_t GetGroupIdByName(const char* name) for (;;) { buffer.resize(size); - if (getgrnam_r(name, &grp, buffer.data(), size, &result) < 0) + if (getgrnam_r(name, &grp, buffer.data(), size, &result) != 0) { if (errno != ERANGE) { diff --git a/src/shared/configfile/configfile.cpp b/src/shared/configfile/configfile.cpp index be3892b9c..04fe10f2d 100644 --- a/src/shared/configfile/configfile.cpp +++ b/src/shared/configfile/configfile.cpp @@ -768,11 +768,11 @@ int ParseConfigFile(std::vector& keys, FILE* file, int flags, const w fprintf(stderr, "expected \"\n"); } - EMIT_USER_WARNING(Localization::MessageConfigExpected("'", filePath, line)); + EMIT_USER_WARNING(Localization::MessageConfigExpected("\"", filePath, line)); // This key value will be overwritten, so we can ignore any malformed values. // However, we can still inform the user of the issue per warning above. - if (!firstMatchedKey || !matchedKey) + if (!firstMatchedKey && !matchedKey) { goto InvalidLine; } diff --git a/src/shared/inc/SocketChannel.h b/src/shared/inc/SocketChannel.h index 7ecf3440e..71ea40b8b 100644 --- a/src/shared/inc/SocketChannel.h +++ b/src/shared/inc/SocketChannel.h @@ -14,6 +14,7 @@ Module Name: #pragma once +#include #include #include "socketshared.h" #include "lxinitshared.h" @@ -41,6 +42,44 @@ constexpr timeval* DefaultSocketTimeout = nullptr; #endif +class SocketChannel; + +class Transaction +{ + friend class SocketChannel; + +public: + ~Transaction() = default; + + NON_COPYABLE(Transaction); + + template + void Send(gsl::span span); + + template + void Send(TMessage& message); + + template + void SendResultMessage(TResult value); + + template + std::pair> ReceiveOrClosed(TTimeout timeout = DefaultSocketTimeout); + + template + TMessage& Receive(gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout); + +private: + Transaction(SocketChannel& channel, uint32_t id) : + m_channel(channel), m_id(id), m_step(static_cast(TRANSACTION_STEP::REQUEST)) + { + } + + SocketChannel& m_channel; + uint32_t m_id; + /** Use uint32_t as step can go beyond FIRST_REPLY */ + uint32_t m_step; +}; + class SocketChannel { @@ -58,13 +97,14 @@ class SocketChannel { m_name = std::move(other.m_name); m_socket = std::move(other.m_socket); - m_received_messages = other.m_received_messages; - m_sent_messages = other.m_sent_messages; #ifdef WIN32 m_exitEvent = std::move(other.m_exitEvent); #endif m_ignore_sequence = other.m_ignore_sequence; + m_sent_non_transaction_messages = other.m_sent_non_transaction_messages; + m_received_non_transaction_messages = other.m_received_non_transaction_messages; + m_transaction_id_seed = other.m_transaction_id_seed.load(); return *this; } @@ -83,7 +123,7 @@ class SocketChannel #endif template - void SendMessage(gsl::span span) + void SendMessage(gsl::span span, uint32_t transactionStep = static_cast(TRANSACTION_STEP::NONE), uint32_t transactionId = 0) { // Ensure that no other thread is using this channel. const std::unique_lock lock{m_sendMutex, std::try_to_lock}; @@ -104,12 +144,20 @@ class SocketChannel THROW_INVALID_ARG_IF(m_name.empty() || span.size() < sizeof(TMessage)); - m_sent_messages++; - auto* header = gslhelpers::try_get_struct(span); WI_ASSERT(header->MessageSize == span.size()); - header->SequenceNumber = m_sent_messages; + if (transactionStep == static_cast(TRANSACTION_STEP::NONE)) + { + m_sent_non_transaction_messages++; + header->TransactionId = m_sent_non_transaction_messages; + header->TransactionStep = static_cast(TRANSACTION_STEP::NONE); + } + else + { + header->TransactionId = transactionId; + header->TransactionStep = transactionStep; + } #ifdef WIN32 @@ -158,7 +206,7 @@ class SocketChannel } template - void SendMessage(TMessage& message) + void SendMessage(TMessage& message, uint32_t transactionStep = static_cast(TRANSACTION_STEP::NONE), uint32_t transactionId = 0) { // Catch situations where the other SendMessage() method should be used const auto& header = GetMessageHeader(message); @@ -172,7 +220,7 @@ class SocketChannel #endif } - SendMessage(gslhelpers::struct_as_writeable_bytes(message)); + SendMessage(gslhelpers::struct_as_writeable_bytes(message), transactionStep, transactionId); } template @@ -187,7 +235,10 @@ class SocketChannel } template - std::pair> ReceiveMessageOrClosed(TTimeout timeout = DefaultSocketTimeout) + std::pair> ReceiveMessageOrClosed( + TTimeout timeout = DefaultSocketTimeout, + uint32_t expectedTransactionStep = static_cast(TRANSACTION_STEP::NONE), + uint32_t expectedTransactionId = 0) { WI_ASSERT(!m_name.empty()); @@ -207,25 +258,195 @@ class SocketChannel #endif } - m_received_messages++; - - auto receivedSpan = ReceiveImpl(TMessage::Type, timeout); - if (receivedSpan.empty()) + gsl::span receivedSpan{}; + for (;;) { + if (expectedTransactionStep == static_cast(TRANSACTION_STEP::NONE)) + { + // Adhere to the old ++ before receive behavior for non-transaction messages. + m_received_non_transaction_messages++; + } + + receivedSpan = ReceiveImpl(TMessage::Type, timeout); + if (receivedSpan.empty()) + { + +#ifdef WIN32 + if (errno == HCS_E_CONNECTION_TIMEOUT) + { + THROW_HR_MSG( + HCS_E_CONNECTION_TIMEOUT, + "Timeout: %u, expected type: %hs, channel: %hs", + timeout, + ToString(TMessage::Type), + m_name.c_str()); + } +#endif + + return {nullptr, {}}; + } + + auto* header = gslhelpers::try_get_struct(receivedSpan); + if (header == nullptr) + { +#ifdef WIN32 + THROW_HR_MSG(E_UNEXPECTED, "Message too small for header: %zd, channel: %hs", receivedSpan.size(), m_name.c_str()); +#else + LOG_ERROR("Message too small for header: {}, channel: {}", receivedSpan.size(), m_name); + THROW_ERRNO(EINVAL); +#endif + } + + if (expectedTransactionStep == static_cast(TRANSACTION_STEP::NONE)) + { + // Handle non-transaction messages with legacy logic. + if (!m_ignore_sequence) + { + if (header->TransactionStep != static_cast(TRANSACTION_STEP::NONE)) + { +#ifdef WIN32 + THROW_HR_MSG( + E_UNEXPECTED, + "Unexpected transaction message received on non-transaction channel: %hs, message type: %hs", + m_name.c_str(), + ToString(header->MessageType)); +#else + LOG_ERROR( + "Unexpected transaction message received on non-transaction channel: {}, message type: {}", + m_name, + ToString(header->MessageType)); + THROW_ERRNO(EINVAL); +#endif + } + if (header->TransactionId != m_received_non_transaction_messages) + { +#ifdef WIN32 + THROW_HR_MSG( + E_UNEXPECTED, + "Unexpected non-transaction message id: %u, expected: %u, channel: %hs", + header->TransactionId, + m_received_non_transaction_messages, + m_name.c_str()); +#else + LOG_ERROR("Unexpected non-transaction message id: {}, expected: {}, channel: {}", header->TransactionId, m_received_non_transaction_messages, m_name); + THROW_ERRNO(EINVAL); +#endif + } + } + break; + } + + // Handle transaction messages + if (header->TransactionStep == static_cast(TRANSACTION_STEP::NONE)) + { + // Skip stale non-transaction messages +#ifdef WIN32 + WSL_LOG( + "DiscardStaleNonTransactionMessage", + TraceLoggingValue(m_name.c_str(), "Name"), + TraceLoggingValue(ToString(header->MessageType), "MessageType"), + TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"), + TraceLoggingValue(header->TransactionId, "StaleNonTransactionId"), + TraceLoggingValue(m_received_non_transaction_messages, "ExpectedNonTransactionId")); +#else + LOG_WARNING( + "Discard stale non-transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, " + "StaleNonTransactionId: {}, ExpectedNonTransactionId: {}", + m_name, + header->MessageType, + TMessage::Type, + header->TransactionId, + m_received_non_transaction_messages); +#endif + continue; + } + + if (expectedTransactionStep == static_cast(TRANSACTION_STEP::REQUEST)) + { + // Skip until we get the next request. No matter the transaction id. + if (header->TransactionStep != static_cast(TRANSACTION_STEP::REQUEST)) + { +#ifdef WIN32 + WSL_LOG( + "DiscardOutOfOrderTransactionMessage", + TraceLoggingValue(m_name.c_str(), "Name"), + TraceLoggingValue(ToString(header->MessageType), "MessageType"), + TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"), + TraceLoggingValue(header->TransactionStep, "StaleTransactionStep"), + TraceLoggingValue(expectedTransactionStep, "ExpectedTransactionStep")); +#else + LOG_WARNING( + "Discard out of order transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, " + "StaleTransactionStep: {}, ExpectedTransactionStep: {}", + m_name, + header->MessageType, + TMessage::Type, + header->TransactionStep, + expectedTransactionStep); +#endif + continue; + } + break; + } + auto diff = static_cast(header->TransactionId - expectedTransactionId); + if (diff < 0) + { + // Skip stale transaction messages #ifdef WIN32 - if (errno == HCS_E_CONNECTION_TIMEOUT) + WSL_LOG( + "DiscardStaleTransactionMessage", + TraceLoggingValue(m_name.c_str(), "Name"), + TraceLoggingValue(ToString(header->MessageType), "MessageType"), + TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"), + TraceLoggingValue(header->TransactionId, "StaleTransactionId"), + TraceLoggingValue(expectedTransactionId, "ExpectedTransactionId")); +#else + LOG_WARNING( + "Discard stale transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, " + "StaleTransactionId: {}, ExpectedTransactionId: {}", + m_name, + header->MessageType, + TMessage::Type, + header->TransactionId, + expectedTransactionId); +#endif + continue; + } + + if (diff > 0) { + // Message is from the future. +#ifdef WIN32 THROW_HR_MSG( - HCS_E_CONNECTION_TIMEOUT, - "Timeout: %d, expected type: %hs, channel: %hs", - timeout, - ToString(TMessage::Type), + E_UNEXPECTED, + "Unexpected transaction message id: %u, expected: %u, channel: %hs", + header->TransactionId, + expectedTransactionId, m_name.c_str()); +#else + LOG_ERROR("Unexpected transaction message id: {}, expected: {}, channel: {}", header->TransactionId, expectedTransactionId, m_name); + THROW_ERRNO(EINVAL); +#endif } + + if (header->TransactionStep != expectedTransactionStep) + { + // Broken transaction. +#ifdef WIN32 + THROW_HR_MSG( + E_UNEXPECTED, + "Unexpected transaction message step: %u, expected: %u, channel: %hs", + header->TransactionStep, + expectedTransactionStep, + m_name.c_str()); +#else + LOG_ERROR("Unexpected transaction message step: {}, expected: {}, channel: {}", header->TransactionStep, expectedTransactionStep, m_name); + THROW_ERRNO(EINVAL); #endif + } - return {nullptr, {}}; + break; } auto* message = gslhelpers::try_get_struct(receivedSpan); @@ -245,7 +466,7 @@ class SocketChannel #endif } - ValidateMessageHeader(GetMessageHeader(*message), TMessage::Type, m_received_messages); + ValidateMessageHeader(GetMessageHeader(*message), TMessage::Type); #ifdef WIN32 WSL_LOG( @@ -262,9 +483,13 @@ class SocketChannel } template - TMessage& ReceiveMessage(gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout) + TMessage& ReceiveMessage( + gsl::span* responseSpan = nullptr, + TTimeout timeout = DefaultSocketTimeout, + uint32_t expectedTransactionStep = static_cast(TRANSACTION_STEP::NONE), + uint32_t expectedTransactionId = 0) { - auto [message, span] = ReceiveMessageOrClosed(timeout); + auto [message, span] = ReceiveMessageOrClosed(timeout, expectedTransactionStep, expectedTransactionId); if (message == nullptr) { #ifdef WIN32 @@ -283,16 +508,28 @@ class SocketChannel return *message; } - template - TSentMessage::TResponse& Transaction(gsl::span message, gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout) + Transaction StartTransaction() + { + uint32_t transactionId = m_transaction_id_seed++; + return wsl::shared::Transaction(*this, transactionId); + } + + Transaction ReceiveTransaction() { - SendMessage(message); + // Transaction id should follow the received one on the receive end. + return wsl::shared::Transaction(*this, 0); + } - return ReceiveMessage(responseSpan, timeout); + template + typename TSentMessage::TResponse& Transaction(gsl::span message, gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout) + { + auto transaction = StartTransaction(); + transaction.Send(message); + return transaction.Receive(responseSpan, timeout); } template - TSentMessage::TResponse& Transaction(TSentMessage& message, gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout) + typename TSentMessage::TResponse& Transaction(TSentMessage& message, gsl::span* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout) { WI_ASSERT(message.Header.MessageSize == sizeof(message)); @@ -359,33 +596,33 @@ class SocketChannel #endif - void ValidateMessageHeader(const MESSAGE_HEADER& header, LX_MESSAGE_TYPE expected, unsigned int expectedSequence) const + void ValidateMessageHeader(const MESSAGE_HEADER& header, LX_MESSAGE_TYPE expected) const { - if (header.MessageSize < sizeof(header) || (expected != LxMiniInitMessageAny && header.MessageType != expected) || - (!m_ignore_sequence && header.SequenceNumber != expectedSequence)) + + if (header.MessageSize < sizeof(header) || (expected != LxMiniInitMessageAny && header.MessageType != expected)) { #ifdef WIN32 THROW_HR_MSG( E_UNEXPECTED, - "Protocol error: Received message size: %u, type: %u, sequence: %u. Expected type: %u, expected sequence: %u, " + "Protocol error: Received message size: %u, type: %u, id: %u, step: %u. Expected type: %u, " "channel: %hs", header.MessageSize, header.MessageType, - header.SequenceNumber, + header.TransactionId, + header.TransactionStep, expected, - expectedSequence, m_name.c_str()); #else LOG_ERROR( - "Protocol error: Received message size: {}, type: {}, sequence: {}. Expected type: {}, expected sequence: {}, " + "Protocol error: Received message size: {}, type: {}, id: {}, step: {}. Expected type: {}, " "channel: {}", header.MessageSize, header.MessageType, - header.SequenceNumber, + header.TransactionId, + header.TransactionStep, expected, - expectedSequence, m_name); THROW_ERRNO(EINVAL); @@ -430,11 +667,65 @@ class SocketChannel HANDLE m_exitEvent{}; #endif - uint32_t m_sent_messages = 0; - uint32_t m_received_messages = 0; + uint32_t m_sent_non_transaction_messages = 0; + uint32_t m_received_non_transaction_messages = 0; + std::atomic m_transaction_id_seed = 0; bool m_ignore_sequence = false; std::string m_name{}; std::mutex m_sendMutex; std::mutex m_receiveMutex; }; -} // namespace wsl::shared \ No newline at end of file + +template +void Transaction::Send(gsl::span span) +{ + m_channel.SendMessage(span, m_step, m_id); + m_step++; +} + +template +void Transaction::Send(TMessage& message) +{ + Send(gslhelpers::struct_as_writeable_bytes(message)); +} + +template +void Transaction::SendResultMessage(TResult value) +{ + RESULT_MESSAGE Result{}; + Result.Header.MessageSize = sizeof(Result); + Result.Header.MessageType = RESULT_MESSAGE::Type; + Result.Result = value; + + Send(Result); +} + +template +std::pair> Transaction::ReceiveOrClosed(TTimeout timeout) +{ + auto result = m_channel.ReceiveMessageOrClosed(timeout, m_step, m_id); + if (m_step == static_cast(TRANSACTION_STEP::REQUEST) && result.first != nullptr) + { + // Use the request's id for the reply side transaction. + MESSAGE_HEADER& header = m_channel.GetMessageHeader(*result.first); + m_id = header.TransactionId; + } + m_step++; + return result; +} + +template +TMessage& Transaction::Receive(gsl::span* responseSpan, TTimeout timeout) +{ + auto& message = m_channel.ReceiveMessage(responseSpan, timeout, m_step, m_id); + if (m_step == static_cast(TRANSACTION_STEP::REQUEST)) + { + // Use the request's id for the reply side transaction. + MESSAGE_HEADER& header = m_channel.GetMessageHeader(message); + m_id = header.TransactionId; + } + m_step++; + return message; +} + +} // namespace wsl::shared diff --git a/src/shared/inc/lxinitshared.h b/src/shared/inc/lxinitshared.h index 77b4ed8f0..1f048a0f0 100644 --- a/src/shared/inc/lxinitshared.h +++ b/src/shared/inc/lxinitshared.h @@ -537,15 +537,23 @@ inline void PrettyPrint(std::stringstream& Out, LX_MESSAGE_TYPE Value) Out << ToString(Value); } +enum class TRANSACTION_STEP : unsigned int +{ + NONE = 0, + REQUEST = 1, + FIRST_REPLY = 2, +}; + struct MESSAGE_HEADER { static inline auto Type = LxMiniInitMessageAny; // Setting this allows using MESSAGE_HEADER to receive any type of message LX_MESSAGE_TYPE MessageType; unsigned int MessageSize; - unsigned int SequenceNumber; + unsigned int TransactionId; + unsigned int TransactionStep; - PRETTY_PRINT(FIELD(MessageType), FIELD(MessageSize), FIELD(SequenceNumber)); + PRETTY_PRINT(FIELD(MessageType), FIELD(MessageSize), FIELD(TransactionId), FIELD(TransactionStep)); }; // @@ -633,7 +641,7 @@ typedef struct _LX_PROCESS_CRASH char Buffer[]; - PRETTY_PRINT(FIELD(Header), FIELD(Timestamp), FIELD(Signal), FIELD(Pid), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), FIELD(Timestamp), FIELD(Signal), FIELD(Pid), BUFFER_FIELD(Buffer)); } LX_PROCESS_CRASH, *PLX_PROCESS_CRASH; @@ -755,7 +763,7 @@ typedef struct _LX_INIT_CREATE_LOGIN_SESSION unsigned int Gid; char Buffer[]; // Contains username - PRETTY_PRINT(FIELD(Header), FIELD(Uid), FIELD(Gid), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), FIELD(Uid), FIELD(Gid), BUFFER_FIELD(Buffer)); } LX_INIT_CREATE_LOGIN_SESSION, *PLX_INIT_CREATE_LOGIN_SESSION; // @@ -770,7 +778,7 @@ typedef struct _LX_INIT_QUERY_ENVIRONMENT_VARIABLE MESSAGE_HEADER Header; char Buffer[]; - PRETTY_PRINT(FIELD(Header), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), BUFFER_FIELD(Buffer)); } LX_INIT_QUERY_ENVIRONMENT_VARIABLE, *PLX_INIT_QUERY_ENVIRONMENT_VARIABLE; typedef struct _LX_GNS_RESULT @@ -793,7 +801,7 @@ typedef struct _LX_GNS_INTERFACE_CONFIGURATION MESSAGE_HEADER Header; char Content[]; - PRETTY_PRINT(FIELD(Header), FIELD(Content)); + PRETTY_PRINT(FIELD(Header), BUFFER_FIELD(Content)); } LX_GNS_INTERFACE_CONFIGURATION, *PLX_GNS_INTERFACE_CONFIGURATION; typedef struct _LX_GNS_NOTIFICATION @@ -805,7 +813,7 @@ typedef struct _LX_GNS_NOTIFICATION GUID AdapterId; char Content[]; - PRETTY_PRINT(FIELD(Header), FIELD(AdapterId), FIELD(Content)); + PRETTY_PRINT(FIELD(Header), FIELD(AdapterId), BUFFER_FIELD(Content)); } LX_GNS_NOTIFICATION, *PLX_GNS_NOTIFICATION; typedef struct _LX_GNS_PORT_ALLOCATION_REQUEST @@ -833,7 +841,7 @@ typedef struct _LX_GNS_SET_PORT_LISTENER PRETTY_PRINT(FIELD(Header), FIELD(HvSocketPort)); } LX_GNS_SET_PORT_LISTENER, *PLX_GNS_SET_PORT_LISTENER; -static_assert(sizeof(LX_GNS_SET_PORT_LISTENER) == 16); +static_assert(sizeof(LX_GNS_SET_PORT_LISTENER) == 20); typedef struct _LX_GNS_PORT_LISTENER_RELAY { @@ -899,7 +907,7 @@ typedef struct _LX_GNS_JSON_MESSAGE MESSAGE_HEADER Header; char Content[]; - PRETTY_PRINT(FIELD(Header), FIELD(Content)); + PRETTY_PRINT(FIELD(Header), BUFFER_FIELD(Content)); } LX_GNS_JSON_MESSAGE, *PLX_GNS_JSON_MESSAGE; using PCLX_INIT_NETWORK_INFORMATION = const LX_INIT_NETWORK_INFORMATION*; @@ -1343,7 +1351,7 @@ typedef struct _LX_MINI_INIT_TELEMETRY_MESSAGE bool ShowDrvFsNotification; char Buffer[]; - PRETTY_PRINT(FIELD(Header), FIELD(ShowDrvFsNotification), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), FIELD(ShowDrvFsNotification), BUFFER_FIELD(Buffer)); } LX_MINI_INIT_TELEMETRY_MESSAGE, *PLX_MINI_INIT_TELEMETRY_MESSAGE; @@ -1374,7 +1382,7 @@ typedef struct _LX_MINI_INIT_UNMOUNT_MESSAGE MESSAGE_HEADER Header; char Buffer[]; - PRETTY_PRINT(FIELD(Header), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), BUFFER_FIELD(Buffer)); } LX_MINI_INIT_UNMOUNT_MESSAGE, *PLX_MINI_INIT_UNMOUNT_MESSAGE; typedef struct _LX_MINI_INIT_DETACH_MESSAGE @@ -1423,7 +1431,7 @@ typedef struct _LX_INIT_GUEST_CAPABILITIES bool SeccompAvailable; char Buffer[]; // Contains the kernel version string - PRETTY_PRINT(FIELD(Header), FIELD(SeccompAvailable), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), FIELD(SeccompAvailable), BUFFER_FIELD(Buffer)); } LX_INIT_GUEST_CAPABILITIES, *PLX_INIT_GUEST_CAPABILITIES; typedef struct _LX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE @@ -1866,7 +1874,7 @@ typedef struct _LX_INIT_QUERY_VM_ID MESSAGE_HEADER Header; char Buffer[]; - PRETTY_PRINT(FIELD(Header), FIELD(Buffer)); + PRETTY_PRINT(FIELD(Header), BUFFER_FIELD(Buffer)); } LX_INIT_QUERY_VM_ID, *PLX_INIT_QUERY_VM_ID; template <> diff --git a/src/shared/inc/message.h b/src/shared/inc/message.h index 2e42a1cb5..64bf8ff75 100644 --- a/src/shared/inc/message.h +++ b/src/shared/inc/message.h @@ -206,12 +206,13 @@ class MessageWriter size_t GetRelativeIndex(unsigned int& Index) { - const size_t Offset = reinterpret_cast(&Index) - reinterpret_cast(m_buffer.data()); + const auto* indexPtr = reinterpret_cast(&Index); + const auto* bufferStart = reinterpret_cast(m_buffer.data()); // Validate that 'Index' is actually within the bounds of our buffer - assert(Offset >= 0 && Offset < m_buffer.size()); + assert(indexPtr >= bufferStart && indexPtr + sizeof(Index) <= bufferStart + m_buffer.size()); - return Offset; + return static_cast(indexPtr - bufferStart); } void WriteRelativeIndex(size_t Offset, unsigned int Value) @@ -220,6 +221,5 @@ class MessageWriter } std::vector m_buffer; - size_t m_offset = 0; }; } // namespace wsl::shared \ No newline at end of file diff --git a/src/shared/inc/prettyprintshared.h b/src/shared/inc/prettyprintshared.h index d06782782..26a208ace 100644 --- a/src/shared/inc/prettyprintshared.h +++ b/src/shared/inc/prettyprintshared.h @@ -17,6 +17,8 @@ Module Name: #pragma once #include +#include +#include #include "defs.h" #include "stringshared.h" @@ -33,6 +35,22 @@ Module Name: #define STRING_ARRAY_FIELD(Name) #Name, (StringArray((char*)(this), Name, Header.MessageSize)) +// Safe pretty-print for flexible array members (char Buffer[]). Bounds the read +// using the struct's Header.MessageSize so it never reads past the received data. +#define BUFFER_FIELD(Name) #Name, PrettyPrintSafeBufferView(this, Header.MessageSize, Name) + +inline std::string_view PrettyPrintSafeBufferView(const void* structBase, unsigned int messageSize, const char* buffer) +{ + const auto offset = static_cast(buffer - reinterpret_cast(structBase)); + if (offset >= messageSize) + { + return ""; + } + + const size_t maxLen = messageSize - offset; + return std::string_view(buffer, strnlen(buffer, maxLen)); +} + #define PRETTY_PRINT(...) \ void PrettyPrintImpl(std::stringstream& Out) const \ { \ @@ -56,7 +74,11 @@ struct StringArray template inline void PrettyPrint(std::stringstream& Out, const T& Value) { - if constexpr (std::is_same_v || std::is_same_v) + if constexpr (std::is_same_v) + { + Out << Value; + } + else if constexpr (std::is_same_v || std::is_same_v) { if (Value == nullptr) { @@ -105,7 +127,7 @@ inline void PrettyPrint(std::stringstream& Out, const T (&Value)[Size]) Out << "["; for (auto i = 0; i < Size; i++) { - if (i > 0 && i < Size - 1) + if (i > 0 && i < Size) { Out << ","; } diff --git a/src/shared/inc/socketshared.h b/src/shared/inc/socketshared.h index 1c9129180..ecd6f3605 100644 --- a/src/shared/inc/socketshared.h +++ b/src/shared/inc/socketshared.h @@ -49,7 +49,7 @@ try #if defined(_MSC_VER) THROW_HR(E_UNEXPECTED); #elif defined(__GNUC__) - THROW_UNEXCEPTED(); + THROW_UNEXPECTED(); #endif } @@ -60,7 +60,7 @@ try #if defined(_MSC_VER) THROW_HR_MSG(E_UNEXPECTED, "Unexpected message size: %llu", MessageSize); #elif defined(__GNUC__) - THROW_UNEXCEPTED(); + THROW_UNEXPECTED(); #endif } @@ -69,7 +69,7 @@ try #if defined(_MSC_VER) THROW_HR_MSG(E_UNEXPECTED, "Message size too large: %llu", MessageSize); #elif defined(__GNUC__) - THROW_UNEXCEPTED(); + THROW_UNEXPECTED(); #endif } @@ -95,18 +95,18 @@ try LOG_HR_MSG( E_UNEXPECTED, - "Socket closed while reading message. Size: %u, type: %i, sequence: %u", + "Socket closed while reading message. Size: %u, type: %i, id: %u", Header->MessageSize, Header->MessageType, - Header->SequenceNumber); + Header->TransactionId); #elif defined(__GNUC__) LOG_ERROR( - "Socket closed while reading message. Size: {}, type: {}, sequence: {}", + "Socket closed while reading message. Size: {}, type: {}, id: {}", Header->MessageSize, Header->MessageType, - Header->SequenceNumber); + Header->TransactionId); #endif diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h index 920c1da7a..eee0bb2fe 100644 --- a/src/shared/inc/stringshared.h +++ b/src/shared/inc/stringshared.h @@ -239,6 +239,11 @@ inline std::string CleanHostname(const std::string_view Hostname) } } + if (result.size() > 64) + { + result.resize(64); + } + while (!result.empty() && (result.back() == '.' || result.back() == '-')) { result.pop_back(); @@ -248,10 +253,6 @@ inline std::string CleanHostname(const std::string_view Hostname) { result = c_defaultHostName; } - else if (result.size() > 64) - { - result.resize(64); - } return result; } diff --git a/src/windows/common/GnsPortTrackerChannel.cpp b/src/windows/common/GnsPortTrackerChannel.cpp index 09f70e8d3..61ea606f9 100644 --- a/src/windows/common/GnsPortTrackerChannel.cpp +++ b/src/windows/common/GnsPortTrackerChannel.cpp @@ -33,7 +33,8 @@ void GnsPortTrackerChannel::Run() { for (;;) { - auto [header, range] = m_channel.ReceiveMessageOrClosed(); + auto transaction = m_channel.ReceiveTransaction(); + auto [header, range] = transaction.ReceiveOrClosed(); if (header == nullptr) { return; @@ -46,7 +47,8 @@ void GnsPortTrackerChannel::Run() const auto* message = gslhelpers::try_get_struct(range); THROW_HR_IF_MSG(E_UNEXPECTED, !message, "Unexpected message size: %i", header->MessageSize); - m_channel.SendResultMessage(m_callback(ConvertPortRequestToSockAddr(message), message->Protocol, message->Allocate)); + transaction.SendResultMessage( + m_callback(ConvertPortRequestToSockAddr(message), message->Protocol, message->Allocate)); } break; case LxGnsMessageIfStateChangeRequest: @@ -55,7 +57,7 @@ void GnsPortTrackerChannel::Run() THROW_HR_IF_MSG(E_UNEXPECTED, !message, "Unexpected message size: %i", header->MessageSize); m_interfaceStateCallback(message->InterfaceName, message->InterfaceUp); - m_channel.SendResultMessage(0); + transaction.SendResultMessage(0); } break; default: diff --git a/src/windows/common/HandleConsoleProgressBar.cpp b/src/windows/common/HandleConsoleProgressBar.cpp index 566fc3404..f03b4807c 100644 --- a/src/windows/common/HandleConsoleProgressBar.cpp +++ b/src/windows/common/HandleConsoleProgressBar.cpp @@ -22,7 +22,7 @@ HandleConsoleProgressBar::HandleConsoleProgressBar(HANDLE handle, std::wstring&& { // If this file isn't a disk file, we can't show actual progress. Just show an indicator in that case LARGE_INTEGER fileSize{}; - if (GetFileType(handle) != FILE_TYPE_DISK || FAILED(GetFileSizeEx(handle, &fileSize))) + if (GetFileType(handle) != FILE_TYPE_DISK || !GetFileSizeEx(handle, &fileSize)) { m_progressBar.emplace(std::move(message)); } diff --git a/src/windows/common/WslCoreNetworkingSupport.cpp b/src/windows/common/WslCoreNetworkingSupport.cpp index d4fd0d943..e195cd39b 100644 --- a/src/windows/common/WslCoreNetworkingSupport.cpp +++ b/src/windows/common/WslCoreNetworkingSupport.cpp @@ -117,11 +117,11 @@ bool wsl::core::networking::IsFlowSteeringSupportedByHns() noexcept allocatePortRange.load(c_computeNetworkModuleName, "HcnReserveGuestNetworkServicePortRange")); static LxssDynamicFunction allocatePort{DynamicFunctionErrorLogs::None}; - RETURN_IF_FAILED_EXPECTED(allocatePortRange.load(c_computeNetworkModuleName, "HcnReserveGuestNetworkServicePort")); + RETURN_IF_FAILED_EXPECTED(allocatePort.load(c_computeNetworkModuleName, "HcnReserveGuestNetworkServicePort")); static LxssDynamicFunction releasePort{DynamicFunctionErrorLogs::None}; RETURN_IF_FAILED_EXPECTED( - allocatePortRange.load(c_computeNetworkModuleName, "HcnReleaseGuestNetworkServicePortReservationHandle")); + releasePort.load(c_computeNetworkModuleName, "HcnReleaseGuestNetworkServicePortReservationHandle")); supported = true; } diff --git a/src/windows/common/WslInstall.cpp b/src/windows/common/WslInstall.cpp index 33fe96bf0..b8ed5bce2 100644 --- a/src/windows/common/WslInstall.cpp +++ b/src/windows/common/WslInstall.cpp @@ -59,6 +59,7 @@ std::vector GetInstalledOptionalComponents() return installedComponents; } + }; // namespace HRESULT WslInstall::InstallDistribution( diff --git a/src/windows/common/filesystem.cpp b/src/windows/common/filesystem.cpp index 2c6c585d6..59236db8f 100644 --- a/src/windows/common/filesystem.cpp +++ b/src/windows/common/filesystem.cpp @@ -838,10 +838,10 @@ std::string wsl::windows::common::filesystem::GetLinuxHostName() { DWORD size = 0; WI_VERIFY(GetComputerNameExA(ComputerNamePhysicalDnsHostname, nullptr, &size) == FALSE); - std::string hostName(size, '\0'); + std::string hostName(size - 1, '\0'); THROW_LAST_ERROR_IF(!GetComputerNameExA(ComputerNamePhysicalDnsHostname, hostName.data(), &size)); - WI_ASSERT((size <= LX_HOST_NAME_MAX) && (hostName.size() == size + 1)); + WI_ASSERT((size <= LX_HOST_NAME_MAX) && (hostName.size() == size)); return wsl::shared::string::CleanHostname(hostName); } diff --git a/src/windows/common/helpers.cpp b/src/windows/common/helpers.cpp index f9542635d..d4e19ec55 100644 --- a/src/windows/common/helpers.cpp +++ b/src/windows/common/helpers.cpp @@ -223,7 +223,7 @@ void wsl::windows::common::helpers::ConnectPipe(_In_ HANDLE Pipe, _In_ DWORD Tim } const auto Result = WaitForMultipleObjects(gsl::narrow_cast(WaitHandles.size()), WaitHandles.data(), FALSE, Timeout); - if (!ExitEvents.empty() && Result > WAIT_OBJECT_0 && Result <= WAIT_OBJECT_0 + WaitHandles.size()) + if (!ExitEvents.empty() && Result > WAIT_OBJECT_0 && Result < WAIT_OBJECT_0 + WaitHandles.size()) { THROW_HR(E_ABORT); } @@ -380,7 +380,7 @@ std::string wsl::windows::common::helpers::GetLinuxTimezone(_In_opt_ HANDLE User THROW_HR_IF_MSG(E_FAIL, (U_FAILURE(status) != false), "%hs", u_errorName(status)); - timezone.resize(buffer.size()); + timezone.resize(size); u_UCharsToChars(buffer.data(), timezone.data(), static_cast(timezone.size())); } CATCH_LOG() diff --git a/src/windows/common/relay.cpp b/src/windows/common/relay.cpp index ee104e4c5..83ca39a4a 100644 --- a/src/windows/common/relay.cpp +++ b/src/windows/common/relay.cpp @@ -213,7 +213,7 @@ bool wsl::windows::common::relay::InterruptableWait(_In_ HANDLE WaitObject, _In_ const DWORD waitResult = WaitForMultipleObjects(gsl::narrow_cast(waitObjects.size()), waitObjects.data(), FALSE, INFINITE); if (waitResult != WAIT_OBJECT_0) { - if (waitResult > WAIT_OBJECT_0 && waitResult <= WAIT_OBJECT_0 + waitObjects.size()) + if (waitResult > WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + waitObjects.size()) { return false; } diff --git a/src/windows/common/socket.cpp b/src/windows/common/socket.cpp index 83e24c74a..c128b7b0d 100644 --- a/src/windows/common/socket.cpp +++ b/src/windows/common/socket.cpp @@ -177,7 +177,7 @@ int wsl::windows::common::socket::Send( Offset += BytesWritten; if (Offset < Buffer.size()) { - WSL_LOG("PartialSocketWrite", TraceLoggingValue(Buffer.size(), "MessagSize"), TraceLoggingValue(Offset, "Offset")); + WSL_LOG("PartialSocketWrite", TraceLoggingValue(Buffer.size(), "MessageSize"), TraceLoggingValue(Offset, "Offset")); } } diff --git a/src/windows/common/wslutil.cpp b/src/windows/common/wslutil.cpp index f46ef3c30..aeca79266 100644 --- a/src/windows/common/wslutil.cpp +++ b/src/windows/common/wslutil.cpp @@ -616,10 +616,36 @@ wsl::windows::common::wslutil::GetDefaultVersion(void) return version; } +namespace { + +// Returns true if Windows Admin Protection (shadow admin) is enabled on +// this system. The message is shown for both elevated and non-elevated +// callers because either side may be missing the other's distributions. +// Caches the DLL lookup on first call. +bool IsAdminProtectionEnabled() +{ + using ShadowAdminEnabledFn = BOOL(WINAPI)(); + static std::optional> s_fn; + static std::once_flag s_initFlag; + + std::call_once(s_initFlag, []() { + LxssDynamicFunction fn{DynamicFunctionErrorLogs::None}; + if (SUCCEEDED(fn.load(L"SecurityHealthUdk.dll", "Shield_LUAIsShadowAdminEnabled"))) + { + s_fn.emplace(std::move(fn)); + } + }); + + return s_fn.has_value() && (*s_fn)(); +} + +} // anonymous namespace + std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result) { ULONG buildNumber = 0; std::wstring kbUrl; + std::wstring errorString; switch (result) { @@ -639,10 +665,12 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result) return Localization::MessageHigherIntegrity(); case WSL_E_DEFAULT_DISTRO_NOT_FOUND: - return Localization::MessageNoDefaultDistro(); + errorString = Localization::MessageNoDefaultDistro(); + break; case WSL_E_DISTRO_NOT_FOUND: - return Localization::MessageDistroNotFound(); + errorString = Localization::MessageDistroNotFound(); + break; case WSL_E_DISTRIBUTION_NAME_NEEDED: return Localization::MessageDistributionNameNeeded(); @@ -779,7 +807,26 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result) } } - return GetSystemErrorString(result); + if (errorString.empty()) + { + return GetSystemErrorString(result); + } + + // If Admin Protection is enabled, prepend an informational message for + // errors that may be caused by the shadow admin's separate registry hive. + try + { + if (IsAdminProtectionEnabled()) + { + auto message = Localization::MessageAdminProtectionEnabled(); + message += L"\n\n"; + message += errorString; + return message; + } + } + CATCH_LOG() + + return errorString; } std::optional> wsl::windows::common::wslutil::GetGitHubAssetFromRelease(const GitHubRelease& Release) diff --git a/src/windows/service/exe/BridgedNetworking.cpp b/src/windows/service/exe/BridgedNetworking.cpp index 7e7b30c71..2c3a580f0 100644 --- a/src/windows/service/exe/BridgedNetworking.cpp +++ b/src/windows/service/exe/BridgedNetworking.cpp @@ -77,7 +77,7 @@ void BridgedNetworking::FillInitialConfiguration(LX_MINI_INIT_NETWORKING_CONFIGU message.NetworkingMode = LxMiniInitNetworkingModeBridged; message.DisableIpv6 = !m_config.EnableIpv6; message.EnableDhcpClient = m_config.EnableDhcp; - message.DhcpTimeout = static_cast(std::round(m_config.DhcpTimeout / 1000)); + message.DhcpTimeout = static_cast(std::round(m_config.DhcpTimeout / 1000.0)); message.PortTrackerType = m_config.EnableLocalhostRelay ? LxMiniInitPortTrackerTypeRelay : LxMiniInitPortTrackerTypeNone; } diff --git a/src/windows/service/exe/DistributionRegistration.cpp b/src/windows/service/exe/DistributionRegistration.cpp index bfda98c2f..03cdc26f0 100644 --- a/src/windows/service/exe/DistributionRegistration.cpp +++ b/src/windows/service/exe/DistributionRegistration.cpp @@ -96,7 +96,6 @@ DistributionRegistration DistributionRegistration::Create( distribution.Write(Property::Version, Version); distribution.Write(Property::BasePath, BasePath); distribution.Write(Property::Flags, Flags); - distribution.Write(Property::Flags, Flags); distribution.Write(Property::DefaultUid, DefaultUID); distribution.Write(Property::RunOOBE, EnableOobe); diff --git a/src/windows/service/exe/LxssCreateProcess.h b/src/windows/service/exe/LxssCreateProcess.h index 8e1c704a4..0bb3e793f 100644 --- a/src/windows/service/exe/LxssCreateProcess.h +++ b/src/windows/service/exe/LxssCreateProcess.h @@ -85,15 +85,15 @@ class LxssCreateProcess wsl::shared::MessageWriter message(LxInitCreateProcess); message.WriteString(message->PathIndex, Path); gsl::copy(as_bytes(gsl::span(ArgumentsData)), message.InsertBuffer(message->CommandLineIndex, ArgumentsData.size())); - channel.SendMessage(message.Span()); + auto transaction = channel.StartTransaction(); + transaction.Send(message.Span()); auto readResult = [&]() { - const auto& message = channel.ReceiveMessage>(nullptr, Timeout); + const auto& message = transaction.Receive>(nullptr, Timeout); return message.Result; }; auto processSocket = wsl::windows::common::hvsocket::Connect(RuntimeId, readResult(), terminatingEvent); - const auto execResult = readResult(); THROW_HR_IF_MSG(E_FAIL, execResult != 0, "Failed to execute '%hs', error=%d", Path, execResult); diff --git a/src/windows/service/exe/LxssUserSession.cpp b/src/windows/service/exe/LxssUserSession.cpp index 6bf8e6e96..a1a8d481b 100644 --- a/src/windows/service/exe/LxssUserSession.cpp +++ b/src/windows/service/exe/LxssUserSession.cpp @@ -891,7 +891,7 @@ HRESULT LxssUserSessionImpl::MountDisk( _Out_ int* Step, _Out_ LPWSTR* MountName) { - ExecutionContext context(Context::DetachDisk); + ExecutionContext context(Context::MountDisk); std::lock_guard lock(m_instanceLock); return wil::ResultFromException([&]() { @@ -1788,47 +1788,48 @@ try std::lock_guard lock(m_instanceLock); const wil::unique_hkey lxssKey = s_OpenLxssUserKey(); const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid); - LXSS_DISTRO_CONFIGURATION configuration = s_GetDistributionConfiguration(registration); + const auto configuration = s_GetDistributionConfiguration(registration); RETURN_HR_IF(WSL_E_WSL2_NEEDED, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE)); - const auto vhdFilePath = configuration.VhdFilePath; - if (m_utilityVm && m_utilityVm->IsVhdAttached(vhdFilePath.c_str())) + const auto& vhdPath = configuration.VhdFilePath; + if (m_utilityVm && m_utilityVm->IsVhdAttached(vhdPath.c_str())) { THROW_HR_WITH_USER_ERROR(WSL_E_DISTRO_NOT_STOPPED, wsl::shared::Localization::MessageVhdInUse()); } - auto diskHandle = wsl::core::filesystem::OpenVhd(vhdFilePath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS); - const auto diskSize = wsl::core::filesystem::GetDiskSize(diskHandle.get()); - - const auto resizingLarger = NewSize > diskSize; - if (resizingLarger) + // If growing the VHD, resize the underlying VHD file before resizing the filesystem. + bool resizingLarger; { - wsl::core::filesystem::ResizeExistingVhd(diskHandle.get(), NewSize, RESIZE_VIRTUAL_DISK_FLAG_NONE); - } + auto runAsUser = wil::CoImpersonateClient(); + auto diskHandle = wsl::core::filesystem::OpenVhd(vhdPath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS); + resizingLarger = NewSize > wsl::core::filesystem::GetDiskSize(diskHandle.get()); - diskHandle.reset(); + if (resizingLarger) + { + wsl::core::filesystem::ResizeExistingVhd(diskHandle.get(), NewSize, RESIZE_VIRTUAL_DISK_FLAG_NONE); + } + } // Ensure VM exists and attach the VHD. _CreateVm(); const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); - const auto lun = m_utilityVm->AttachDisk(vhdFilePath.c_str(), WslCoreVm::DiskType::VHD, {}, true, userToken.get()); + const auto lun = m_utilityVm->AttachDisk(vhdPath.c_str(), WslCoreVm::DiskType::VHD, {}, true, userToken.get()); // Resize the underlying filesystem. // // N.B. Passing zero as the size causes the resize to consume all available space on the block device. { - auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { m_utilityVm->EjectVhd(vhdFilePath.c_str()); }); + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { m_utilityVm->EjectVhd(vhdPath.c_str()); }); m_utilityVm->ResizeDistribution(lun, OutputHandle, resizingLarger ? 0 : NewSize); } // If shrinking the VHD, resize the underlying VHD file. This is only supported for .vhdx files. // // N.B. RESIZE_VIRTUAL_DISK_FLAG_ALLOW_UNSAFE_VIRTUAL_SIZE is required because vhdmp can't validate that the minimum safe ext4 size. - if (!resizingLarger && - wsl::shared::string::IsEqual(vhdFilePath.extension().c_str(), wsl::windows::common::wslutil::c_vhdxFileExtension, true)) + if (!resizingLarger && wsl::shared::string::IsEqual(vhdPath.extension().c_str(), wsl::windows::common::wslutil::c_vhdxFileExtension, true)) { - const auto diskHandle = - wsl::core::filesystem::OpenVhd(vhdFilePath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS); + auto runAsUser = wil::CoImpersonateClient(); + const auto diskHandle = wsl::core::filesystem::OpenVhd(vhdPath.c_str(), VIRTUAL_DISK_ACCESS_GET_INFO | VIRTUAL_DISK_ACCESS_METAOPS); wsl::core::filesystem::ResizeExistingVhd(diskHandle.get(), NewSize, RESIZE_VIRTUAL_DISK_FLAG_ALLOW_UNSAFE_VIRTUAL_SIZE); } diff --git a/src/windows/service/exe/WslCoreInstance.cpp b/src/windows/service/exe/WslCoreInstance.cpp index 74fe3168d..98b7379c4 100644 --- a/src/windows/service/exe/WslCoreInstance.cpp +++ b/src/windows/service/exe/WslCoreInstance.cpp @@ -344,7 +344,8 @@ void WslCoreInstance::UpdateTimezone() wsl::windows::common::helpers::GenerateTimezoneUpdateMessage(wsl::windows::common::helpers::GetLinuxTimezone(m_userToken.get())); auto lock = m_initChannel->Lock(); - m_initChannel->GetChannel().SendMessage(gsl::make_span(message)); + auto transaction = m_initChannel->GetChannel().StartTransaction(); + transaction.Send(gsl::make_span(message)); } ULONG64 WslCoreInstance::GetLifetimeManagerId() const @@ -389,11 +390,12 @@ void WslCoreInstance::Initialize() auto config = wsl::windows::common::helpers::GenerateConfigurationMessage( m_configuration.Name, fixedDrives, m_defaultUid, timezone, {}, m_featureFlags, drvfsMount); - m_initChannel->GetChannel().SendMessage(gsl::span(config)); + auto transaction = m_initChannel->GetChannel().StartTransaction(); + transaction.Send(gsl::span(config)); // Init replies with information about the distribution. gsl::span span; - const auto& response = m_initChannel->GetChannel().ReceiveMessage(&span); + const auto& response = transaction.Receive(&span); m_defaultUid = response.DefaultUid; m_plan9Port = response.Plan9Port; m_distributionInfo.PidNamespace = response.PidNamespace; @@ -473,8 +475,9 @@ bool WslCoreInstance::RequestStop(_In_ bool Force) terminateMessage.Header.MessageSize = sizeof(terminateMessage); terminateMessage.Force = Force; - m_initChannel->GetChannel().SendMessage(terminateMessage); - auto [message, span] = m_initChannel->GetChannel().ReceiveMessageOrClosed>(m_socketTimeout); + auto transaction = m_initChannel->GetChannel().StartTransaction(); + transaction.Send(terminateMessage); + auto [message, span] = transaction.ReceiveOrClosed>(m_socketTimeout); if (message) { shutdown = message->Result; diff --git a/src/windows/service/exe/WslCoreVm.cpp b/src/windows/service/exe/WslCoreVm.cpp index 76a598bba..b22a63009 100644 --- a/src/windows/service/exe/WslCoreVm.cpp +++ b/src/windows/service/exe/WslCoreVm.cpp @@ -520,7 +520,8 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken message.WriteString(message->KernelModulesListOffset, m_vmConfig.KernelModulesList); message->DnsTunnelingIpAddress = m_vmConfig.DnsTunnelingIpAddress.value_or(0); - m_miniInitChannel.SendMessage(message.Span()); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message.Span()); { ExecutionContext context(Context::ConfigureNetworking); @@ -1058,15 +1059,25 @@ void WslCoreVm::CollectCrashDumps(wil::unique_socket&& listenSocket) const auto channel = wsl::shared::SocketChannel{std::move(socket.value()), "crash_dump", m_terminatingEvent.get()}; - const auto& message = channel.ReceiveMessage(); - const char* process = reinterpret_cast(&message.Buffer); + auto transaction = channel.ReceiveTransaction(); + gsl::span responseSpan; + const auto& message = transaction.Receive(&responseSpan); + + // Safely extract the process name from the flexible array member. + // The buffer may not be NUL-terminated, so bound the length to the received span size. + const auto bufferSize = responseSpan.size_bytes() - offsetof(LX_PROCESS_CRASH, Buffer); + const std::string process(message.Buffer, strnlen(message.Buffer, bufferSize)); constexpr auto dumpExtension = ".dmp"; constexpr auto dumpPrefix = "wsl-crash"; auto filename = std::format("{}-{}-{}-{}-{}{}", dumpPrefix, message.Timestamp, message.Pid, process, message.Signal, dumpExtension); - std::replace_if(filename.begin(), filename.end(), [](auto e) { return !std::isalnum(e) && e != '.' && e != '-'; }, '_'); + std::replace_if( + filename.begin(), + filename.end(), + [](char e) { return !std::isalnum(static_cast(e)) && e != '.' && e != '-'; }, + '_'); auto fullPath = m_vmConfig.CrashDumpFolder / filename; @@ -1077,7 +1088,7 @@ void WslCoreVm::CollectCrashDumps(wil::unique_socket&& listenSocket) const TraceLoggingValue(fullPath.c_str(), "FullPath"), TraceLoggingValue(message.Pid, "Pid"), TraceLoggingValue(message.Signal, "Signal"), - TraceLoggingValue(process, "process")); + TraceLoggingValue(process.c_str(), "process")); auto runAsUser = wil::impersonate_token(m_userToken.get()); @@ -1106,7 +1117,7 @@ void WslCoreVm::CollectCrashDumps(wil::unique_socket&& listenSocket) const wil::unique_hfile file{CreateFileW(fullPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr)}; THROW_LAST_ERROR_IF(!file); - channel.SendResultMessage(0); + transaction.SendResultMessage(0); wsl::windows::common::relay::InterruptableRelay(reinterpret_cast(channel.Socket()), file.get(), nullptr); } @@ -1166,7 +1177,8 @@ std::shared_ptr WslCoreVm::CreateInstance( message.WriteString(message->SharedMemoryRootOffset, sharedMemoryRoot); message.WriteString(message->InstallPathOffset, installPath); message.WriteString(message->UserProfileOffset, userProfile); - m_miniInitChannel.SendMessage(message.Span()); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message.Span()); return CreateInstanceInternal( InstanceId, Configuration, ReceiveTimeout, DefaultUid, ClientLifetimeId, WI_IsFlagSet(flags, LxMiniInitMessageFlagLaunchSystemDistro), ConnectPort); @@ -1804,7 +1816,8 @@ void WslCoreVm::InitializeGuest() } // Send the message. - m_miniInitChannel.SendMessage(message.Span()); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message.Span()); // If port tracker or localhost relay are enabled, establish a connection with the guest and start processing messages. switch (message->NetworkingConfiguration.PortTrackerType) @@ -1830,6 +1843,7 @@ void WslCoreVm::InitializeGuest() const auto errorString = wsl::windows::common::wslutil::GetSystemErrorString(result); EMIT_USER_WARNING(wsl::shared::Localization::MessageLocalhostRelayFailed(errorString)); } + break; } default: @@ -1940,7 +1954,8 @@ WslCoreVm::DiskMountResult WslCoreVm::MountDiskLockHeld( message.WriteString(message->OptionsOffset, Options); // Send the message. - m_miniInitChannel.SendMessage(message.Span()); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message.Span()); // Accept a connection from mini_init wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", m_terminatingEvent.get()}; @@ -2068,7 +2083,8 @@ void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId) { auto lock = m_lock.lock_exclusive(); - m_miniInitChannel.SendMessage(message); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message); channel = { AcceptConnection(m_vmConfig.KernelBootTimeout), "WaitForPmem", @@ -2378,7 +2394,8 @@ void WslCoreVm::ResizeDistribution(_In_ ULONG Lun, _In_ HANDLE OutputHandle, _In message.ScsiLun = Lun; message.NewSize = NewSize; - m_miniInitChannel.SendMessage(message); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message); wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "ResizeDistribution", m_terminatingEvent.get()}; auto outputChannel = AcceptConnection(m_vmConfig.KernelBootTimeout); @@ -2455,7 +2472,8 @@ std::pair WslCoreVm::UnmountDisk(_In_ const AttachedDis message.Header.MessageSize = sizeof(message); message.ScsiLun = State.Lun; - m_miniInitChannel.SendMessage(message); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message); // Accept a connection from mini_init. wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", m_terminatingEvent.get()}; @@ -2470,7 +2488,8 @@ std::pair WslCoreVm::UnmountVolume(_In_ const AttachedD message.WriteString(Name); // Send the message. - m_miniInitChannel.SendMessage(message.Span()); + auto transaction = m_miniInitChannel.StartTransaction(); + transaction.Send(message.Span()); // Accept a connection from mini_init. wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", m_terminatingEvent.get()}; @@ -2536,7 +2555,8 @@ try { wsl::windows::common::wslutil::SetThreadDescription(L"VirtioFs - Request"); - auto [message, span] = channel.ReceiveMessageOrClosed(); + auto transaction = channel.ReceiveTransaction(); + auto [message, span] = transaction.ReceiveOrClosed(); if (message == nullptr) { return; @@ -2550,7 +2570,7 @@ try response.WriteString(response->TagOffset, tag); response.WriteString(response->SourceOffset, source); - channel.SendMessage(response.Span()); + transaction.Send(response.Span()); }; if (message->MessageType == LxInitMessageAddVirtioFsDevice) @@ -2793,6 +2813,24 @@ void WslCoreVm::ValidateNetworkingMode() } } + // If mirrored networking was requested, ensure IPv6 is not disabled on the host using registry, + // as this is not supported by mirrored networking. + // Note: Disabling IPv6 using Set-NetAdapterBinding is supported. + if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored) + { + constexpr DWORD c_ipv6Disabled = 0xFF; + DWORD disabledComponents = 0; + wil::reg::get_value_dword_nothrow( + HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters", L"DisabledComponents", &disabledComponents); + + if (disabledComponents == c_ipv6Disabled) + { + m_vmConfig.NetworkingMode = NetworkingMode::Nat; + EMIT_USER_WARNING(Localization::MessageMirroredNetworkingNotSupportedReason( + Localization::MessageMirroredNetworkingNotSupportedIpv6Disabled())); + } + } + // If mirrored networking was requested, ensure it is supported by the OS and guest kernel. if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored) { diff --git a/src/windows/wslinstaller/exe/WslInstaller.cpp b/src/windows/wslinstaller/exe/WslInstaller.cpp index f370d6498..7ac796d6c 100644 --- a/src/windows/wslinstaller/exe/WslInstaller.cpp +++ b/src/windows/wslinstaller/exe/WslInstaller.cpp @@ -31,18 +31,26 @@ std::wstring GetMsiPackagePath() return (wsl::windows::common::wslutil::GetBasePath() / L"wsl.msi").wstring(); } -std::optional GetUpgradeLogFileLocation() +struct UpgradeLogInfo +{ + std::wstring path; + bool fromRegistry; // true when the path was explicitly configured via UpgradeLogFile registry value +}; + +std::optional GetUpgradeLogFileLocation() try { const auto key = wsl::windows::common::registry::OpenLxssMachineKey(); const auto path = wsl::windows::common::registry::ReadString(key.get(), L"MSI", L"UpgradeLogFile", L""); if (path.empty()) { - return {}; + // Default to the same path used by wsl --update so all MSI logs + // are collected from one location by the diagnostic script. + return UpgradeLogInfo{(std::filesystem::temp_directory_path() / L"wsl-install-logs.txt").wstring(), false}; } // A canonical path is required because msiexec doesn't like symlinks. - return std::filesystem::weakly_canonical(path); + return UpgradeLogInfo{std::filesystem::weakly_canonical(path), true}; } catch (...) { @@ -54,6 +62,16 @@ std::pair InstallMsipackageImpl() { const auto logFile = GetUpgradeLogFileLocation(); + // Delete MSI log on success, preserve on failure for diagnostics (same as wsl --update). + // When the UpgradeLogFile registry value is set, always keep the log — the registry key + // is explicitly designed to retain MSI logs across installs. + auto clearLogs = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { + if (logFile.has_value() && !logFile->fromRegistry) + { + LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile->path.c_str())); + } + }); + std::wstring errors; auto messageCallback = [&errors](INSTALLMESSAGE type, LPCWSTR message) { switch (type) @@ -77,7 +95,7 @@ std::pair InstallMsipackageImpl() }; auto result = wsl::windows::common::install::UpgradeViaMsi( - GetMsiPackagePath().c_str(), L"SKIPMSIX=1", logFile.has_value() ? logFile->c_str() : nullptr, messageCallback); + GetMsiPackagePath().c_str(), L"SKIPMSIX=1", logFile.has_value() ? logFile->path.c_str() : nullptr, messageCallback); // ERROR_SUCCESS_REBOOT_REQUIRED (3010) means the install succeeded but some files // will be replaced on the next reboot. Treat as success since the service runs @@ -94,6 +112,11 @@ std::pair InstallMsipackageImpl() TraceLoggingValue(rebootRequired, "rebootRequired"), TraceLoggingValue(errors.c_str(), "errorMessage")); + if (result != ERROR_SUCCESS && result != ERROR_SUCCESS_REBOOT_REQUIRED) + { + clearLogs.release(); + } + return {result, errors}; } diff --git a/src/windows/wslrelay/localhost.cpp b/src/windows/wslrelay/localhost.cpp index 22eed11bd..a4b9c2c52 100644 --- a/src/windows/wslrelay/localhost.cpp +++ b/src/windows/wslrelay/localhost.cpp @@ -33,7 +33,7 @@ struct in6_addr_linux } u; }; -const uint16_t ADDR6_MASK3 = ~in6_addr_linux(IN6ADDR_LOOPBACK_INIT).u.addr32[3]; +const uint32_t ADDR6_MASK3 = ~in6_addr_linux(IN6ADDR_LOOPBACK_INIT).u.addr32[3]; const uint32_t N_ADDR_LOOPBACK = ntohl(INADDR_LOOPBACK); const uint32_t N_ADDR_ANY = ntohl(INADDR_ANY); @@ -81,7 +81,8 @@ void wsl::windows::wslrelay::localhost::RelayWorker(_In_ wsl::shared::SocketChan for (;;) { - auto [Message, Span] = Channel.ReceiveMessageOrClosed(); + auto Transaction = Channel.ReceiveTransaction(); + auto [Message, Span] = Transaction.ReceiveOrClosed(); if (Message == nullptr) { break; @@ -151,7 +152,7 @@ void wsl::windows::wslrelay::localhost::RelayWorker(_In_ wsl::shared::SocketChan } } - Channel.SendMessage(Response); + Transaction.Send(Response); break; } diff --git a/src/windows/wslsettings/CMakeLists.txt b/src/windows/wslsettings/CMakeLists.txt index 6b1983527..2734b5bbb 100644 --- a/src/windows/wslsettings/CMakeLists.txt +++ b/src/windows/wslsettings/CMakeLists.txt @@ -127,6 +127,7 @@ add_executable( Views/Settings/NetworkingPage.xaml.cs Views/Settings/OptionalFeaturesPage.xaml Views/Settings/OptionalFeaturesPage.xaml.cs + Views/Settings/SettingsApplyHelper.cs Views/Settings/ShellPage.xaml Views/Settings/ShellPage.xaml.cs Windows/OOBEWindow.xaml diff --git a/src/windows/wslsettings/Contracts/Services/IWslConfigService.cs b/src/windows/wslsettings/Contracts/Services/IWslConfigService.cs index a0154bf71..cb59aa670 100644 --- a/src/windows/wslsettings/Contracts/Services/IWslConfigService.cs +++ b/src/windows/wslsettings/Contracts/Services/IWslConfigService.cs @@ -5,9 +5,33 @@ namespace WslSettings.Contracts.Services; public interface IWslConfigService { IWslConfigSetting GetWslConfigSetting(WslConfigEntry wslConfigEntry, bool defaultSetting = false); + + /// + /// Stages a config value change in memory. The value is only written to disk when + /// is called. + /// uint SetWslConfigSetting(IWslConfigSetting setting); + + /// + /// True when there are in-app changes that have not yet been committed to disk. + /// + bool HasPendingChanges { get; } + + /// + /// Returns pending changes as (entry, new value) pairs for UI display. + /// + IReadOnlyList GetPendingChanges(); + uint CommitPendingChanges(); public delegate void WslConfigChangedEventHandler(); event WslConfigChangedEventHandler WslConfigChanged; + public delegate void PendingChangesChangedEventHandler(); + event PendingChangesChangedEventHandler PendingChangesChanged; +} + +public sealed class WslConfigPendingChange +{ + public WslConfigEntry ConfigEntry { get; init; } + public required object PendingValue { get; init; } } public interface IWslConfigSetting @@ -21,4 +45,47 @@ public interface IWslConfigSetting MemoryReclaimMode MemoryReclaimModeValue { get; } uint SetValue(object? value); bool Equals(object? obj); +} + +/// +/// Describes which union field a uses inside the native WslConfigSetting struct. +/// +public enum WslConfigValueKind +{ + Bool, + Int32, + UInt64, + String, + NetworkingConfiguration, + MemoryReclaimMode, +} + +public static class WslConfigEntryExtensions +{ + /// + /// Returns the that describes which union field this entry uses. + /// Keep this in sync with the native WslConfigSetting union in WslCoreConfigInterface.h. + /// + public static WslConfigValueKind GetValueKind(this WslConfigEntry entry) => entry switch + { + WslConfigEntry.SwapFilePath or + WslConfigEntry.IgnoredPorts or + WslConfigEntry.KernelPath or + WslConfigEntry.SystemDistroPath or + WslConfigEntry.KernelModulesPath => WslConfigValueKind.String, + + WslConfigEntry.ProcessorCount or + WslConfigEntry.InitialAutoProxyTimeout or + WslConfigEntry.VMIdleTimeout => WslConfigValueKind.Int32, + + WslConfigEntry.MemorySizeBytes or + WslConfigEntry.SwapSizeBytes or + WslConfigEntry.VhdSizeBytes => WslConfigValueKind.UInt64, + + WslConfigEntry.NetworkingMode => WslConfigValueKind.NetworkingConfiguration, + + WslConfigEntry.AutoMemoryReclaim => WslConfigValueKind.MemoryReclaimMode, + + _ => WslConfigValueKind.Bool, + }; } \ No newline at end of file diff --git a/src/windows/wslsettings/Services/WslConfigService.cs b/src/windows/wslsettings/Services/WslConfigService.cs index ce1a526e9..7b007a87f 100644 --- a/src/windows/wslsettings/Services/WslConfigService.cs +++ b/src/windows/wslsettings/Services/WslConfigService.cs @@ -1,205 +1,415 @@ -// Copyright (C) Microsoft Corporation. All rights reserved. - -using WslSettings.Contracts.Services; -using static WslSettings.Contracts.Services.IWslConfigService; - -namespace WslSettings.Services; - -public class WslConfigService : IWslConfigService -{ - private WslConfig? _wslConfig { get; set; } - private WslConfig? _wslConfigDefaults { get; init; } - private readonly object? _wslCoreConfigInterfaceLockObj = null; - private FileSystemWatcher? _wslConfigFileSystemWatcher = null; - - public WslConfigService() - { - string filePath = WslCoreConfigInterface.GetWslConfigFilePath(); - _wslConfig = WslCoreConfigInterface.CreateWslConfig(filePath); - _wslConfigDefaults = WslCoreConfigInterface.CreateWslConfig(null); - _wslCoreConfigInterfaceLockObj = new object(); - _wslConfigFileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(filePath) ?? string.Empty, Path.GetFileName(filePath)); - - _wslConfigFileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; - - _wslConfigFileSystemWatcher.Changed += OnWslConfigFileChanged; - _wslConfigFileSystemWatcher.Deleted += OnWslConfigFileChanged; - _wslConfigFileSystemWatcher.Renamed += OnWslConfigFileChanged; - - _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; - } - - ~WslConfigService() - { - WslCoreConfigInterface.FreeWslConfig(_wslConfig); - WslCoreConfigInterface.FreeWslConfig(_wslConfigDefaults); - } - - public IWslConfigSetting GetWslConfigSetting(WslConfigEntry wslConfigEntry, bool defaultSetting) - { - WslConfigSettingManaged? wslConfigSetting = null; - lock (_wslCoreConfigInterfaceLockObj!) - { - wslConfigSetting = new WslConfigSettingManaged(WslCoreConfigInterface.GetWslConfigSetting(defaultSetting ? _wslConfigDefaults : _wslConfig, wslConfigEntry)); - } - - return wslConfigSetting; - } - - public uint SetWslConfigSetting(IWslConfigSetting wslConfigSetting) - { - var wslConfigSettingsManaged = wslConfigSetting as WslConfigSettingManaged; - if (wslConfigSettingsManaged == null) - { - throw new ArgumentNullException("wslConfigSetting"); - } - - uint result = 0; - lock (_wslCoreConfigInterfaceLockObj!) - { - _wslConfigFileSystemWatcher!.EnableRaisingEvents = false; - result = WslCoreConfigInterface.SetWslConfigSetting(_wslConfig, wslConfigSettingsManaged.ConfigSetting); - _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; - } - - return result; - } - - private WslConfigChangedEventHandler? _onWslConfigChangedHandler = null; - public event WslConfigChangedEventHandler WslConfigChanged - { - add - { - _onWslConfigChangedHandler += value; - } - remove - { - _onWslConfigChangedHandler -= value; - } - } - - private void OnWslConfigFileChanged(object sender, FileSystemEventArgs e) - { - lock (_wslCoreConfigInterfaceLockObj!) - { - _wslConfigFileSystemWatcher!.EnableRaisingEvents = false; - WslCoreConfigInterface.FreeWslConfig(_wslConfig); - _wslConfig = WslCoreConfigInterface.CreateWslConfig(WslCoreConfigInterface.GetWslConfigFilePath()); - _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; - } - - _onWslConfigChangedHandler?.Invoke(); - } -} - -public partial class WslConfigSettingManaged : IWslConfigSetting -{ - public WslConfigSettingManaged(WslConfigSetting wslConfigSetting) - { - ConfigSetting = wslConfigSetting; - } - - ~WslConfigSettingManaged() - { - ConfigSetting.Dispose(); - } - - public WslConfigSetting ConfigSetting { get; init; } - public WslConfigEntry ConfigEntry { get { return ConfigSetting.ConfigEntry; } } - public string StringValue { get { return ConfigSetting.StringValue; } } - public ulong UInt64Value { get { return ConfigSetting.UInt64Value; } } - public int Int32Value { get { return ConfigSetting.Int32Value; } } - public bool BoolValue { get { return ConfigSetting.BoolValue; } } - public NetworkingConfiguration NetworkingConfigurationValue { get { return ConfigSetting.NetworkingConfigurationValue; } } - public MemoryReclaimMode MemoryReclaimModeValue { get { return ConfigSetting.MemoryReclaimModeValue; } } - -#nullable enable - public uint SetValue(object? value) - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - if ("".GetType() == value.GetType()) - { - ConfigSetting.StringValue = (string)value; - } - else if (ConfigSetting.UInt64Value.GetType() == value.GetType()) - { - ConfigSetting.UInt64Value = (ulong)value; - } - else if (ConfigSetting.Int32Value.GetType() == value.GetType()) - { - ConfigSetting.Int32Value = (int)value; - } - else if (ConfigSetting.BoolValue.GetType() == value.GetType()) - { - ConfigSetting.BoolValue = (bool)value; - } - else if (ConfigSetting.NetworkingConfigurationValue.GetType() == value.GetType()) - { - ConfigSetting.NetworkingConfigurationValue = (NetworkingConfiguration)value; - } - else if (ConfigSetting.MemoryReclaimModeValue.GetType() == value.GetType()) - { - ConfigSetting.MemoryReclaimModeValue = (MemoryReclaimMode)value; - } - else - { - throw new InvalidDataException(); - } - - return App.GetService().SetWslConfigSetting(this); - } - - public override bool Equals(object? value) - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - // Special handling for byte values. Compare using MB since in the UI the user works with MB. - if (ConfigSetting.ConfigEntry == WslConfigEntry.MemorySizeBytes || - ConfigSetting.ConfigEntry == WslConfigEntry.SwapSizeBytes || - ConfigSetting.ConfigEntry == WslConfigEntry.VhdSizeBytes) - { - return ((ulong)value / Constants.MB) == (UInt64Value / Constants.MB); - } - - if ("".GetType() == value.GetType()) - { - return ConfigSetting.StringValue == (string)value; - } - else if (ConfigSetting.UInt64Value.GetType() == value.GetType()) - { - return ConfigSetting.UInt64Value == (ulong)value; - } - else if (ConfigSetting.Int32Value.GetType() == value.GetType()) - { - return ConfigSetting.Int32Value == (int)value; - } - else if (ConfigSetting.BoolValue.GetType() == value.GetType()) - { - return ConfigSetting.BoolValue == (bool)value; - } - else if (ConfigSetting.NetworkingConfigurationValue.GetType() == value.GetType()) - { - return ConfigSetting.NetworkingConfigurationValue == (NetworkingConfiguration)value; - } - else if (ConfigSetting.MemoryReclaimModeValue.GetType() == value.GetType()) - { - return ConfigSetting.MemoryReclaimModeValue == (MemoryReclaimMode)value; - } - else - { - throw new InvalidDataException(); - } - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } -} \ No newline at end of file +// Copyright (C) Microsoft Corporation. All rights reserved. + +using WslSettings.Contracts.Services; +using static WslSettings.Contracts.Services.IWslConfigService; + +namespace WslSettings.Services; + +public class WslConfigService : IWslConfigService, IDisposable +{ + private WslConfig? _wslConfig { get; set; } + private WslConfig? _wslConfigDefaults { get; init; } + private readonly object? _wslCoreConfigInterfaceLockObj = null; + private FileSystemWatcher? _wslConfigFileSystemWatcher = null; + + // Pending changes: stores only entries the user has changed in-app but not yet committed. + // Values are plain managed objects (bool, int, ulong, string, or enum) — no native clones needed. + private readonly Dictionary _pendingValues = new(); + + public WslConfigService() + { + string filePath = WslCoreConfigInterface.GetWslConfigFilePath(); + _wslConfig = WslCoreConfigInterface.CreateWslConfig(filePath); + _wslConfigDefaults = WslCoreConfigInterface.CreateWslConfig(null); + _wslCoreConfigInterfaceLockObj = new object(); + _wslConfigFileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(filePath) ?? string.Empty, Path.GetFileName(filePath)); + + _wslConfigFileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; + + _wslConfigFileSystemWatcher.Changed += OnWslConfigFileChanged; + _wslConfigFileSystemWatcher.Deleted += OnWslConfigFileChanged; + _wslConfigFileSystemWatcher.Renamed += OnWslConfigFileChanged; + + _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; + } + + ~WslConfigService() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + lock (_wslCoreConfigInterfaceLockObj!) + { + if (disposing && _wslConfigFileSystemWatcher != null) + { + _wslConfigFileSystemWatcher.EnableRaisingEvents = false; + _wslConfigFileSystemWatcher.Changed -= OnWslConfigFileChanged; + _wslConfigFileSystemWatcher.Deleted -= OnWslConfigFileChanged; + _wslConfigFileSystemWatcher.Renamed -= OnWslConfigFileChanged; + _wslConfigFileSystemWatcher.Dispose(); + _wslConfigFileSystemWatcher = null; + } + + WslCoreConfigInterface.FreeWslConfig(_wslConfig); + WslCoreConfigInterface.FreeWslConfig(_wslConfigDefaults); + } + } + + public IWslConfigSetting GetWslConfigSetting(WslConfigEntry wslConfigEntry, bool defaultSetting) + { + if (defaultSetting) + { + return GetPersistedWslConfigSetting(wslConfigEntry, defaultSetting: true); + } + + lock (_wslCoreConfigInterfaceLockObj!) + { + if (_pendingValues.TryGetValue(wslConfigEntry, out var pendingValue)) + { + // Build a native setting from the pending managed value for the caller + var setting = GetPersistedWslConfigSetting(wslConfigEntry, defaultSetting: false); + setting.SetValueDirect(pendingValue); + return setting; + } + + return GetPersistedWslConfigSetting(wslConfigEntry, defaultSetting: false); + } + } + + public uint SetWslConfigSetting(IWslConfigSetting wslConfigSetting) + { + var settingManaged = wslConfigSetting as WslConfigSettingManaged; + if (settingManaged == null) + { + throw new ArgumentNullException(nameof(wslConfigSetting)); + } + + bool pendingStateChanged; + lock (_wslCoreConfigInterfaceLockObj!) + { + var hadPendingBefore = _pendingValues.Count > 0; + + var persisted = GetPersistedWslConfigSetting(settingManaged.ConfigEntry, defaultSetting: false); + try + { + bool isChanged = !persisted.Equals(settingManaged.GetValueAsObject()); + + if (isChanged) + { + _pendingValues[settingManaged.ConfigEntry] = settingManaged.GetValueAsObject(); + } + else + { + _pendingValues.Remove(settingManaged.ConfigEntry); + } + } + finally + { + persisted.ConfigSetting.Dispose(); + } + + var hasPendingAfter = _pendingValues.Count > 0; + pendingStateChanged = hadPendingBefore != hasPendingAfter; + } + + if (pendingStateChanged) + { + _onPendingChangesChangedHandler?.Invoke(); + } + + return 0; + } + + public bool HasPendingChanges + { + get + { + lock (_wslCoreConfigInterfaceLockObj!) + { + return _pendingValues.Count > 0; + } + } + } + + public IReadOnlyList GetPendingChanges() + { + lock (_wslCoreConfigInterfaceLockObj!) + { + var changes = new List(_pendingValues.Count); + foreach (var (entry, value) in _pendingValues) + { + changes.Add(new WslConfigPendingChange + { + ConfigEntry = entry, + PendingValue = value, + }); + } + return changes; + } + } + + public uint CommitPendingChanges() + { + uint result = 0; + bool pendingStateChanged; + + lock (_wslCoreConfigInterfaceLockObj!) + { + if (_pendingValues.Count == 0) + { + return 0; + } + + _wslConfigFileSystemWatcher!.EnableRaisingEvents = false; + try + { + var committed = new List(); + foreach (var (entry, value) in _pendingValues) + { + var setting = GetPersistedWslConfigSetting(entry, defaultSetting: false); + try + { + setting.SetValueDirect(value); + result = WslCoreConfigInterface.SetWslConfigSetting(_wslConfig, setting.ConfigSetting); + } + finally + { + setting.ConfigSetting.Dispose(); + } + + if (result != 0) + { + break; + } + + committed.Add(entry); + } + + ReloadConfig_NoLock(); + + if (result == 0) + { + _pendingValues.Clear(); + } + else + { + // Partial failure - only remove successfully-committed entries + // so unapplied entries remain pending. + foreach (var entry in committed) + { + _pendingValues.Remove(entry); + } + } + } + finally + { + _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; + } + + // We entered with pending changes. Fire only if they're now empty (full success). + pendingStateChanged = _pendingValues.Count == 0; + } + + if (pendingStateChanged) + { + _onPendingChangesChangedHandler?.Invoke(); + } + _onWslConfigChangedHandler?.Invoke(); + return result; + } + + private WslConfigChangedEventHandler? _onWslConfigChangedHandler = null; + public event WslConfigChangedEventHandler WslConfigChanged + { + add + { + _onWslConfigChangedHandler += value; + } + remove + { + _onWslConfigChangedHandler -= value; + } + } + + private PendingChangesChangedEventHandler? _onPendingChangesChangedHandler = null; + public event PendingChangesChangedEventHandler PendingChangesChanged + { + add + { + _onPendingChangesChangedHandler += value; + } + remove + { + _onPendingChangesChangedHandler -= value; + } + } + + private void OnWslConfigFileChanged(object sender, FileSystemEventArgs e) + { + bool hadPending; + lock (_wslCoreConfigInterfaceLockObj!) + { + hadPending = _pendingValues.Count > 0; + _wslConfigFileSystemWatcher!.EnableRaisingEvents = false; + try + { + ReloadConfig_NoLock(); + _pendingValues.Clear(); + } + finally + { + _wslConfigFileSystemWatcher!.EnableRaisingEvents = true; + } + } + + if (hadPending) + { + _onPendingChangesChangedHandler?.Invoke(); + } + _onWslConfigChangedHandler?.Invoke(); + } + + // Read a single setting from the native config layer (either the user's .wslconfig or built-in defaults). + private WslConfigSettingManaged GetPersistedWslConfigSetting(WslConfigEntry wslConfigEntry, bool defaultSetting) + { + return new WslConfigSettingManaged(WslCoreConfigInterface.GetWslConfigSetting(defaultSetting ? _wslConfigDefaults : _wslConfig, wslConfigEntry)); + } + + private void ReloadConfig_NoLock() + { + WslCoreConfigInterface.FreeWslConfig(_wslConfig); + _wslConfig = WslCoreConfigInterface.CreateWslConfig(WslCoreConfigInterface.GetWslConfigFilePath()); + } +} + +public partial class WslConfigSettingManaged : IWslConfigSetting +{ + public WslConfigSettingManaged(WslConfigSetting wslConfigSetting) + { + ConfigSetting = wslConfigSetting; + } + + ~WslConfigSettingManaged() + { + ConfigSetting.Dispose(); + } + + public WslConfigSetting ConfigSetting { get; init; } + public WslConfigEntry ConfigEntry { get { return ConfigSetting.ConfigEntry; } } + public string StringValue { get { return ConfigSetting.StringValue; } } + public ulong UInt64Value { get { return ConfigSetting.UInt64Value; } } + public int Int32Value { get { return ConfigSetting.Int32Value; } } + public bool BoolValue { get { return ConfigSetting.BoolValue; } } + public NetworkingConfiguration NetworkingConfigurationValue { get { return ConfigSetting.NetworkingConfigurationValue; } } + public MemoryReclaimMode MemoryReclaimModeValue { get { return ConfigSetting.MemoryReclaimModeValue; } } + + public object GetValueAsObject() + { + switch (ConfigSetting.ConfigEntry.GetValueKind()) + { + case WslConfigValueKind.String: + return ConfigSetting.StringValue; + case WslConfigValueKind.Int32: + return ConfigSetting.Int32Value; + case WslConfigValueKind.UInt64: + return ConfigSetting.UInt64Value; + case WslConfigValueKind.NetworkingConfiguration: + return ConfigSetting.NetworkingConfigurationValue; + case WslConfigValueKind.MemoryReclaimMode: + return ConfigSetting.MemoryReclaimModeValue; + default: + return ConfigSetting.BoolValue; + } + } + + // Apply a plain managed value to the native setting without going through the service. + public void SetValueDirect(object value) + { + switch (ConfigSetting.ConfigEntry.GetValueKind()) + { + case WslConfigValueKind.String: + ConfigSetting.StringValue = (string)value; + break; + case WslConfigValueKind.Int32: + ConfigSetting.Int32Value = (int)value; + break; + case WslConfigValueKind.UInt64: + ConfigSetting.UInt64Value = (ulong)value; + break; + case WslConfigValueKind.NetworkingConfiguration: + ConfigSetting.NetworkingConfigurationValue = (NetworkingConfiguration)value; + break; + case WslConfigValueKind.MemoryReclaimMode: + ConfigSetting.MemoryReclaimModeValue = (MemoryReclaimMode)value; + break; + default: + ConfigSetting.BoolValue = (bool)value; + break; + } + } + +#nullable enable + public uint SetValue(object? value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + switch (ConfigSetting.ConfigEntry.GetValueKind()) + { + case WslConfigValueKind.String: + ConfigSetting.StringValue = (string)value; + break; + case WslConfigValueKind.Int32: + ConfigSetting.Int32Value = (int)value; + break; + case WslConfigValueKind.UInt64: + ConfigSetting.UInt64Value = (ulong)value; + break; + case WslConfigValueKind.NetworkingConfiguration: + ConfigSetting.NetworkingConfigurationValue = (NetworkingConfiguration)value; + break; + case WslConfigValueKind.MemoryReclaimMode: + ConfigSetting.MemoryReclaimModeValue = (MemoryReclaimMode)value; + break; + default: + ConfigSetting.BoolValue = (bool)value; + break; + } + + return App.GetService().SetWslConfigSetting(this); + } + + public override bool Equals(object? value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + // Special handling for byte values. Compare using MB since in the UI the user works with MB. + if (ConfigSetting.ConfigEntry == WslConfigEntry.MemorySizeBytes || + ConfigSetting.ConfigEntry == WslConfigEntry.SwapSizeBytes || + ConfigSetting.ConfigEntry == WslConfigEntry.VhdSizeBytes) + { + return ((ulong)value / Constants.MB) == (UInt64Value / Constants.MB); + } + + // object.Equals handles null on either side (returns true if both null, false if one null). + return object.Equals(GetValueAsObject(), value); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } +} diff --git a/src/windows/wslsettings/Views/Settings/OptionalFeaturesPage.xaml.cs b/src/windows/wslsettings/Views/Settings/OptionalFeaturesPage.xaml.cs index 5fc3d2b27..0bfc9ae64 100644 --- a/src/windows/wslsettings/Views/Settings/OptionalFeaturesPage.xaml.cs +++ b/src/windows/wslsettings/Views/Settings/OptionalFeaturesPage.xaml.cs @@ -62,4 +62,4 @@ private void VMIdleTimeoutTextBox_TextChanged(object sender, TextChangedEventArg TextBox? textBox = sender as TextBox; ViewModel.SetVMIdleTimeout_ResetEnabled(textBox!.Text); } -} \ No newline at end of file +} diff --git a/src/windows/wslsettings/Views/Settings/SettingsApplyHelper.cs b/src/windows/wslsettings/Views/Settings/SettingsApplyHelper.cs new file mode 100644 index 000000000..1b3f4cfb8 --- /dev/null +++ b/src/windows/wslsettings/Views/Settings/SettingsApplyHelper.cs @@ -0,0 +1,221 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using WslSettings.Contracts.Services; + +namespace WslSettings.Views.Settings; + +internal static class SettingsApplyHelper +{ + public static async Task ShowApplyChangesDialogAsync(XamlRoot xamlRoot) + { + var wslConfigService = App.GetService(); + var pendingChanges = wslConfigService.GetPendingChanges(); + if (pendingChanges.Count == 0) + { + return; + } + + var changeLines = new List(pendingChanges.Count); + foreach (var change in pendingChanges) + { + changeLines.Add($"- {GetSettingDisplayName(change.ConfigEntry)}: {FormatValue(change.ConfigEntry, change.PendingValue)}"); + } + + var contentText = string.Join(Environment.NewLine, changeLines); + + var contentPanel = new StackPanel { Spacing = 8 }; + contentPanel.Children.Add(new TextBlock + { + Text = "Settings_ApplyChangesDialogDescription".GetLocalized(), + TextWrapping = TextWrapping.Wrap, + }); + contentPanel.Children.Add(new TextBlock + { + Text = contentText, + TextWrapping = TextWrapping.Wrap, + FontWeight = Microsoft.UI.Text.FontWeights.SemiBold, + }); + + var dialog = new ContentDialog + { + XamlRoot = xamlRoot, + Title = "Settings_ApplyChangesDialogTitle".GetLocalized(), + Content = contentPanel, + PrimaryButtonText = "Settings_ApplyChangesDialogShutdownButton".GetLocalized(), + SecondaryButtonText = "Settings_ApplyChangesDialogLaterButton".GetLocalized(), + CloseButtonText = "Settings_ApplyChangesDialogCloseButton".GetLocalized(), + DefaultButton = ContentDialogButton.Primary, + }; + + var result = await dialog.ShowAsync(); + if (result == ContentDialogResult.Primary) + { + // "Shutdown WSL now" — commit to disk and shutdown + var commitResult = wslConfigService.CommitPendingChanges(); + if (commitResult != 0) + { + await ShowFailureDialogAsync(xamlRoot, string.Format("Settings_ApplyChangesDialogCommitFailed".GetLocalized(), commitResult)); + return; + } + + try + { + var wslPath = Path.Combine(AppContext.BaseDirectory, "..", "wsl.exe"); + Process.Start(new ProcessStartInfo + { + FileName = wslPath, + Arguments = "--shutdown", + CreateNoWindow = true, + UseShellExecute = false, + })?.Dispose(); + } + catch (Win32Exception ex) + { + await ShowFailureDialogAsync(xamlRoot, string.Format("Settings_ApplyChangesDialogShutdownFailed".GetLocalized(), ex.Message)); + } + catch (InvalidOperationException ex) + { + await ShowFailureDialogAsync(xamlRoot, string.Format("Settings_ApplyChangesDialogShutdownFailed".GetLocalized(), ex.Message)); + } + } + else if (result == ContentDialogResult.Secondary) + { + // "Later" — commit to disk but don't shutdown; settings apply on next WSL restart + var commitResult = wslConfigService.CommitPendingChanges(); + if (commitResult != 0) + { + await ShowFailureDialogAsync(xamlRoot, string.Format("Settings_ApplyChangesDialogCommitFailed".GetLocalized(), commitResult)); + } + } + // Esc / close — do nothing, leave changes pending + } + + private static async Task ShowFailureDialogAsync(XamlRoot xamlRoot, string message) + { + var dialog = new ContentDialog + { + XamlRoot = xamlRoot, + Title = "Settings_ApplyChangesDialogFailedTitle".GetLocalized(), + Content = new TextBlock + { + Text = message, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 620, + }, + CloseButtonText = "Settings_ApplyChangesDialogCloseButton".GetLocalized(), + DefaultButton = ContentDialogButton.Close, + }; + + await dialog.ShowAsync(); + } + + private static string GetSettingDisplayName(WslConfigEntry entry) + { + // Use existing Settings page resource keys so dialog matches page terminology + if (SettingDisplayNameResources.TryGetValue(entry, out var resourceKey)) + { + var localized = resourceKey.GetLocalized(); + if (!string.IsNullOrEmpty(localized) && localized != resourceKey) + { + return localized; + } + } + + // Fallback: type name (keeps something useful even if resx missing) + return entry.ToString(); + } + + private static readonly IReadOnlyDictionary SettingDisplayNameResources = + new Dictionary + { + // ResourceLoader.GetString() requires '/' (not '.') as the separator for x:Uid property resources + { WslConfigEntry.ProcessorCount, "Settings_ProcCount/Header" }, + { WslConfigEntry.MemorySizeBytes, "Settings_MemorySize/Header" }, + { WslConfigEntry.SwapSizeBytes, "Settings_SwapSize/Header" }, + { WslConfigEntry.SwapFilePath, "Settings_SwapFilePath/Header" }, + { WslConfigEntry.VhdSizeBytes, "Settings_DefaultVHDSize/Header" }, + { WslConfigEntry.NetworkingMode, "Settings_NetworkingMode/Header" }, + { WslConfigEntry.FirewallEnabled, "Settings_HyperVFirewall/Header" }, + { WslConfigEntry.IgnoredPorts, "Settings_IgnoredPorts/Header" }, + { WslConfigEntry.LocalhostForwardingEnabled, "Settings_LocalhostForwarding/Header" }, + { WslConfigEntry.HostAddressLoopbackEnabled, "Settings_HostAddressLoopback/Header" }, + { WslConfigEntry.AutoProxyEnabled, "Settings_AutoProxy/Header" }, + { WslConfigEntry.InitialAutoProxyTimeout, "Settings_InitialAutoProxyTimeout/Header" }, + { WslConfigEntry.DNSProxyEnabled, "Settings_DNSProxy/Header" }, + { WslConfigEntry.DNSTunnelingEnabled, "Settings_DNSTunneling/Header" }, + { WslConfigEntry.BestEffortDNSParsingEnabled, "Settings_BestEffortDNS/Header" }, + { WslConfigEntry.AutoMemoryReclaim, "Settings_AutoMemoryReclaim/Header" }, + { WslConfigEntry.GUIApplicationsEnabled, "Settings_GUIApplications/Header" }, + { WslConfigEntry.NestedVirtualizationEnabled, "Settings_NestedVirtualization/Header" }, + { WslConfigEntry.SafeModeEnabled, "Settings_SafeMode/Header" }, + { WslConfigEntry.SparseVHDEnabled, "Settings_SparseVHD/Header" }, + { WslConfigEntry.VMIdleTimeout, "Settings_VMIdleTimeout/Header" }, + { WslConfigEntry.DebugConsoleEnabled, "Settings_DebugConsole/Header" }, + { WslConfigEntry.HardwarePerformanceCountersEnabled, "Settings_HWPerfCounters/Header" }, + { WslConfigEntry.KernelPath, "Settings_CustomKernelPath/Header" }, + { WslConfigEntry.SystemDistroPath, "Settings_CustomSystemDistroPath/Header" }, + { WslConfigEntry.KernelModulesPath, "Settings_CustomKernelModulesPath/Header" }, + }; + + private static string FormatValue(WslConfigEntry entry, object value) + { + switch (entry.GetValueKind()) + { + case WslConfigValueKind.UInt64: + return string.Format("Settings_MegabyteStringFormat".GetLocalized(), (ulong)value / Constants.MB); + case WslConfigValueKind.Int32: + switch (entry) + { + case WslConfigEntry.InitialAutoProxyTimeout: + case WslConfigEntry.VMIdleTimeout: + return string.Format("Settings_MillisecondsStringFormat".GetLocalized(), (int)value); + default: + return ((int)value).ToString(); + } + case WslConfigValueKind.String: + return (string?)value ?? string.Empty; + case WslConfigValueKind.NetworkingConfiguration: + return FormatEnum((NetworkingConfiguration)value); + case WslConfigValueKind.MemoryReclaimMode: + return FormatEnum((MemoryReclaimMode)value); + default: + return FormatBool((bool)value); + } + } + + private static string FormatBool(bool value) + { + var localized = value + ? "Settings_BooleanTrueText".GetLocalized() + : "Settings_BooleanFalseText".GetLocalized(); + return string.IsNullOrEmpty(localized) + ? (value ? bool.TrueString : bool.FalseString) + : localized; + } + + private static string FormatEnum(TEnum value) where TEnum : struct, Enum + { + // Try resource lookup first using the pattern "Settings_{EnumTypeName}_{EnumValue}". + // GetLocalized throws COMException when the key doesn't exist, so fall back to the raw name. + try + { + var resourceKey = $"Settings_{typeof(TEnum).Name}_{value}"; + var localized = resourceKey.GetLocalized(); + if (!string.IsNullOrEmpty(localized) && localized != resourceKey) + { + return localized; + } + } + catch (COMException) + { + } + + return value.ToString(); + } +} diff --git a/src/windows/wslsettings/Views/Settings/ShellPage.xaml b/src/windows/wslsettings/Views/Settings/ShellPage.xaml index 473e4716d..051984ec7 100644 --- a/src/windows/wslsettings/Views/Settings/ShellPage.xaml +++ b/src/windows/wslsettings/Views/Settings/ShellPage.xaml @@ -162,7 +162,14 @@ + + + + +