diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 61526d673..82f88ce95 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -205,6 +205,34 @@ Install using 'wsl.exe {} <Distro>'. Importing distribution + + Portable distribution '{}' successfully mounted from '{}'. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Failed to mount portable distribution from '{}'. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Portable distribution '{}' successfully unmounted. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Distribution '{}' is not a portable distribution. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Portable distribution '{}' created successfully at '{}'. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Failed to create portable distribution at '{}'. + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Portable path must be on removable media: {} Use the --allow-fixed flag to allow portable distributions on fixed drives for development/testing. + {FixedPlaceholder="{}"}{Locked="--allow-fixed "}Command line arguments, file names and string inserts should not be translated + {} has been downloaded. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt index 90c50d02f..0a98418a3 100644 --- a/src/windows/common/CMakeLists.txt +++ b/src/windows/common/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES helpers.cpp interop.cpp ExecutionContext.cpp + PortableDistribution.cpp socket.cpp hvsocket.cpp Localization.cpp @@ -69,6 +70,7 @@ set(HEADERS HandleConsoleProgressBar.h interop.hpp ExecutionContext.h + PortableDistribution.h socket.hpp hvsocket.hpp LxssMessagePort.h diff --git a/src/windows/common/PortableDistribution.cpp b/src/windows/common/PortableDistribution.cpp new file mode 100644 index 000000000..b67b0ddc1 --- /dev/null +++ b/src/windows/common/PortableDistribution.cpp @@ -0,0 +1,455 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + PortableDistribution.cpp + +Abstract: + + This file contains the implementation of portable WSL distribution functionality. + +--*/ + +#include "precomp.h" +#include "PortableDistribution.h" +#include "svccomm.hpp" +#include "registry.hpp" +#include "filesystem.hpp" +#include "string.hpp" +#include "helpers.hpp" +#include +#include + +namespace wsl::windows::common::portable { + +constexpr inline auto c_portableMetadataFileName = L"wsl-portable.json"; +constexpr inline auto c_portableRegistryValue = L"PortableBasePath"; +constexpr inline auto c_portableFlagValue = L"IsPortable"; +constexpr inline auto c_portableTemporaryValue = L"IsTemporary"; + +bool IsRemovableDrive(_In_ const std::filesystem::path& path, _In_ bool allowFixed) +{ + // Get the root path of the drive + auto rootPath = path.root_path(); + if (rootPath.empty()) + { + // If no root, try to get the absolute path + std::error_code ec; + auto absPath = std::filesystem::absolute(path, ec); + if (ec) + { + return false; + } + rootPath = absPath.root_path(); + } + + // For Volume GUID paths like \\?\Volume{UUID}\, extract the volume + auto pathStr = rootPath.wstring(); + if (IsVolumeGuidPath(pathStr)) + { + // Extract volume GUID and check its properties + // Volume GUID format: \\?\Volume{GUID}\ + auto volumeStart = pathStr.find(L"Volume{"); + if (volumeStart != std::wstring::npos) + { + auto volumeEnd = pathStr.find(L"}", volumeStart); + if (volumeEnd != std::wstring::npos) + { + // Extract the volume path (including the trailing backslash) + volumeEnd = pathStr.find(L"\\", volumeEnd); + if (volumeEnd != std::wstring::npos) + { + pathStr = pathStr.substr(0, volumeEnd + 1); + } + } + } + } + + const UINT driveType = GetDriveTypeW(pathStr.c_str()); + + // Restrict to removable media by default for security + // Allow fixed drives only when explicitly requested (for development/testing) + if (driveType == DRIVE_REMOVABLE) + { + return true; + } + + return allowFixed && (driveType == DRIVE_FIXED); +} + +bool IsVolumeGuidPath(_In_ const std::wstring& path) +{ + // Check if path starts with \\?\Volume{ format + return path.find(L"\\\\?\\Volume{") == 0 || path.find(L"\\??\\Volume{") == 0; +} + +std::filesystem::path NormalizePortablePath( + _In_ const std::filesystem::path& basePath, + _In_ const std::filesystem::path& targetPath) +{ + std::error_code ec; + auto relativePath = std::filesystem::relative(targetPath, basePath, ec); + + if (!ec && !relativePath.empty()) + { + return relativePath; + } + + // If we can't make it relative, return absolute path + return std::filesystem::absolute(targetPath, ec); +} + +PortableDistributionMetadata ReadPortableMetadata(_In_ const std::filesystem::path& metadataPath) +{ + std::ifstream file(metadataPath); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !file.is_open()); + + nlohmann::json jsonData; + try + { + file >> jsonData; + } + catch (const nlohmann::json::exception& e) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), "Failed to parse portable metadata: %s", e.what()); + } + + PortableDistributionMetadata metadata; + try + { + metadata = jsonData.get(); + } + catch (const nlohmann::json::exception& e) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), "Invalid portable metadata format: %s", e.what()); + } + + return metadata; +} + +void WritePortableMetadata(_In_ const std::filesystem::path& metadataPath, _In_ const PortableDistributionMetadata& metadata) +{ + nlohmann::json jsonData = metadata; + + std::ofstream file(metadataPath); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !file.is_open()); + + try + { + file << jsonData.dump(4); // Pretty print with 4-space indentation + } + catch (const std::exception& e) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT), "Failed to write portable metadata: %s", e.what()); + } +} + +PortableMountResult MountPortableDistribution( + _In_ const std::filesystem::path& portablePath, + _In_opt_ LPCWSTR distroName, + _In_ bool temporary, + _In_ bool allowFixed) +{ + // Validate the portable path + ValidatePortablePath(portablePath, allowFixed); + + // Look for metadata file + auto metadataPath = portablePath / c_portableMetadataFileName; + + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + !std::filesystem::exists(metadataPath), + "Portable metadata file not found. Expected: %ls", + metadataPath.c_str()); + + // Read metadata + auto metadata = ReadPortableMetadata(metadataPath); + + // Use provided name or fall back to metadata name + std::wstring actualName = distroName ? distroName : metadata.Name; + + // Find the VHDX file + std::filesystem::path vhdxPath; + if (metadata.VhdxPath.has_value()) + { + vhdxPath = portablePath / metadata.VhdxPath.value(); + } + else + { + // Try to find a VHDX file in the directory + for (const auto& entry : std::filesystem::directory_iterator(portablePath)) + { + if (entry.is_regular_file() && entry.path().extension() == L".vhdx") + { + vhdxPath = entry.path(); + break; + } + } + } + + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + vhdxPath.empty() || !std::filesystem::exists(vhdxPath), + "VHDX file not found in portable directory"); + + // Register the distribution + wsl::windows::common::SvcComm service; + ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_VHD | LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE; + + // Import the distribution in-place + GUID distroGuid; + try + { + distroGuid = service.ImportDistributionInplace(actualName.c_str(), vhdxPath.c_str()); + } + catch (...) + { + // If import fails, try to provide helpful error message + THROW_HR_MSG( + wil::ResultFromCaughtException(), + "Failed to mount portable distribution from %ls", + vhdxPath.c_str()); + } + + // Store portable metadata in registry + try + { + const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); + const auto guidString = wsl::shared::string::GuidToString(distroGuid); + const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), false); + + // Mark as portable + wsl::windows::common::registry::WriteDword(distroKey.get(), nullptr, c_portableFlagValue, 1); + + // Store base path for cleanup + wsl::windows::common::registry::WriteString( + distroKey.get(), + nullptr, + c_portableRegistryValue, + portablePath.wstring().c_str()); + + // Track temporary flag for potential auto-cleanup on reboot/logout + if (temporary) + { + wsl::windows::common::registry::WriteDword(distroKey.get(), nullptr, c_portableTemporaryValue, 1); + } + } + catch (...) + { + // If we can't write registry, unregister and fail + try + { + service.UnregisterDistribution(&distroGuid); + } + catch (...) {} + + throw; + } + + PortableMountResult result; + result.DistroGuid = distroGuid; + result.DistroName = actualName; + result.VhdxPath = vhdxPath; + result.NewlyCreated = false; + + return result; +} + +void UnmountPortableDistribution(_In_ const GUID& distroGuid, _In_ bool removeRegistration) +{ + if (!IsPortableDistribution(distroGuid)) + { + return; + } + + wsl::windows::common::SvcComm service; + + // Terminate any running instances + try + { + service.TerminateInstance(&distroGuid); + } + catch (...) {} + + // Unregister the distribution + if (removeRegistration) + { + try + { + service.UnregisterDistribution(&distroGuid); + } + catch (...) {} + + // Clean up registry entries + try + { + const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); + const auto guidString = wsl::shared::string::GuidToString(distroGuid); + RegDeleteKeyW(lxssKey.get(), guidString.c_str()); + } + catch (...) {} + } +} + +bool IsPortableDistribution(_In_ const GUID& distroGuid) +{ + try + { + const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); + const auto guidString = wsl::shared::string::GuidToString(distroGuid); + const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), true); + + if (!distroKey) + { + return false; + } + + DWORD isPortable = 0; + if (SUCCEEDED(wsl::windows::common::registry::ReadDword(distroKey.get(), nullptr, c_portableFlagValue, isPortable))) + { + return isPortable != 0; + } + } + catch (...) {} + + return false; +} + +std::optional GetPortableBasePath(_In_ const GUID& distroGuid) +{ + try + { + const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); + const auto guidString = wsl::shared::string::GuidToString(distroGuid); + const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), true); + + if (!distroKey) + { + return std::nullopt; + } + + wil::unique_cotaskmem_string pathStr; + if (SUCCEEDED(wsl::windows::common::registry::ReadString(distroKey.get(), nullptr, c_portableRegistryValue, pathStr))) + { + return std::filesystem::path(pathStr.get()); + } + } + catch (...) {} + + return std::nullopt; +} + +void ValidatePortablePath(_In_ const std::filesystem::path& path, _In_ bool allowFixed) +{ + // Check if path exists + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), + !std::filesystem::exists(path), + "Portable path does not exist: %ls", + path.c_str()); + + // Check if it's a directory + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_DIRECTORY), + !std::filesystem::is_directory(path), + "Portable path must be a directory: %ls", + path.c_str()); + + // Check if it's on removable media (restrict to removable by default) + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), + !IsRemovableDrive(path, allowFixed), + "Portable path must be on removable media: %ls. To allow fixed drives, use the --allow-fixed flag.", + path.c_str()); +} + +void CreatePortableDistribution( + _In_ const std::filesystem::path& portablePath, + _In_ LPCWSTR distroName, + _In_ const std::filesystem::path& sourceFile, + _In_ ULONG version, + _In_ ULONG flags, + _In_ bool allowFixed) +{ + // Validate portable path + ValidatePortablePath(portablePath, allowFixed); + + // Check if metadata already exists + auto metadataPath = portablePath / c_portableMetadataFileName; + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_FILE_EXISTS), + std::filesystem::exists(metadataPath), + "Portable distribution already exists at: %ls", + portablePath.c_str()); + + // Determine target VHDX path + std::wstring vhdxFileName = distroName; + vhdxFileName += L".vhdx"; + auto vhdxPath = portablePath / vhdxFileName; + + // Import the distribution using WSL service + // We'll register it temporarily to create the VHDX, then manually clean up the registry + wsl::windows::common::SvcComm service; + + wil::unique_hfile sourceFileHandle; + sourceFileHandle.reset(CreateFileW( + sourceFile.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr)); + + THROW_LAST_ERROR_IF(!sourceFileHandle); + + // Create the VHDX in the portable location + // The RegisterDistribution service handles tar extraction and VHDX creation + auto [guid, name] = service.RegisterDistribution( + distroName, + version, + sourceFileHandle.get(), + portablePath.c_str(), + flags | LXSS_IMPORT_DISTRO_FLAGS_VHD); + + // Clean up the registry entry without deleting the VHDX file + // We do this manually to avoid the full UnregisterDistribution which would delete the VHDX + try + { + // Open the LXSS registry key + auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); + + // Convert GUID to string for registry key name + auto guidStr = wsl::windows::common::string::GuidToString(guid); + + // Delete only the distribution's registry key, leaving the VHDX intact + wsl::windows::common::registry::DeleteKey(lxssKey.get(), guidStr.c_str()); + + // Note: We intentionally do NOT call UnregisterDistribution here because + // it would delete the VHDX file we just created. Instead, we manually remove + // just the registry entry, leaving the VHDX for portable use. + } + catch (...) + { + // If cleanup fails, log but continue - the VHDX was created successfully + // The orphaned registry entry will be cleaned up if the user tries to use + // the distribution and it doesn't exist + LOG_CAUGHT_EXCEPTION(); + } + + // Create portable metadata + PortableDistributionMetadata metadata; + metadata.Name = distroName; + metadata.FriendlyName = distroName; + metadata.VhdxPath = vhdxFileName; + metadata.Version = version; + metadata.DefaultUid = 1000; // Standard default + metadata.IsPortable = true; + + // Write metadata + WritePortableMetadata(metadataPath, metadata); +} + +} // namespace wsl::windows::common::portable diff --git a/src/windows/common/PortableDistribution.h b/src/windows/common/PortableDistribution.h new file mode 100644 index 000000000..d5c7402b2 --- /dev/null +++ b/src/windows/common/PortableDistribution.h @@ -0,0 +1,106 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + PortableDistribution.h + +Abstract: + + This file contains definitions and functions for portable WSL distributions + that can run from removable media (USB drives, external disks, etc.). + +--*/ + +#pragma once + +#include +#include +#include +#include +#include "JsonUtils.h" + +namespace wsl::windows::common::portable { + +// Metadata structure for portable distributions stored in wsl-portable.json +struct PortableDistributionMetadata +{ + std::wstring Name; // Distribution name + std::wstring FriendlyName; // Human-readable name + std::optional VhdxPath; // Relative or absolute path to VHDX file + ULONG Version; // WSL version (1 or 2) + ULONG DefaultUid; // Default user ID + std::optional Guid; // Distribution GUID (generated on first mount) + std::optional DefaultUser; // Default username + bool IsPortable; // Flag indicating this is a portable distribution + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + PortableDistributionMetadata, + Name, + FriendlyName, + VhdxPath, + Version, + DefaultUid, + Guid, + DefaultUser, + IsPortable); +}; + +// Structure to hold portable mount results +struct PortableMountResult +{ + GUID DistroGuid; + std::wstring DistroName; + std::filesystem::path VhdxPath; + bool NewlyCreated; +}; + +// Check if a path is on removable media +// Set allowFixed to true to permit fixed drives for development/testing +bool IsRemovableDrive(_In_ const std::filesystem::path& path, _In_ bool allowFixed = false); + +// Check if a path is using Volume GUID format (\\?\Volume{UUID}\...) +bool IsVolumeGuidPath(_In_ const std::wstring& path); + +// Normalize a path for portable storage (convert to relative if possible) +std::filesystem::path NormalizePortablePath(_In_ const std::filesystem::path& basePath, _In_ const std::filesystem::path& targetPath); + +// Read portable distribution metadata from wsl-portable.json +PortableDistributionMetadata ReadPortableMetadata(_In_ const std::filesystem::path& metadataPath); + +// Write portable distribution metadata to wsl-portable.json +void WritePortableMetadata(_In_ const std::filesystem::path& metadataPath, _In_ const PortableDistributionMetadata& metadata); + +// Mount a portable distribution from removable media +PortableMountResult MountPortableDistribution( + _In_ const std::filesystem::path& portablePath, + _In_opt_ LPCWSTR distroName = nullptr, + _In_ bool temporary = false, + _In_ bool allowFixed = false); + +// Unmount and cleanup a portable distribution +void UnmountPortableDistribution(_In_ const GUID& distroGuid, _In_ bool removeRegistration = true); + +// Check if a distribution is registered as portable +bool IsPortableDistribution(_In_ const GUID& distroGuid); + +// Get the portable base path for a portable distribution +std::optional GetPortableBasePath(_In_ const GUID& distroGuid); + +// Validate that a path is suitable for portable WSL usage +void ValidatePortablePath(_In_ const std::filesystem::path& path, _In_ bool allowFixed = false); + +// Create a new portable distribution from a tar/vhdx file +// This function temporarily registers the distribution to extract the tar and create the VHDX, +// then manually removes only the registry entry (not the VHDX file) to make it portable. +// The VHDX remains on disk and can be mounted later using MountPortableDistribution. +void CreatePortableDistribution( + _In_ const std::filesystem::path& portablePath, + _In_ LPCWSTR distroName, + _In_ const std::filesystem::path& sourceFile, + _In_ ULONG version, + _In_ ULONG flags, + _In_ bool allowFixed = false); + +} // namespace wsl::windows::common::portable diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 04d167a39..00d813af0 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -17,6 +17,7 @@ Module Name: #include "HandleConsoleProgressBar.h" #include "Distribution.h" #include "CommandLine.h" +#include "PortableDistribution.h" #include #define BASH_PATH L"/bin/bash" @@ -128,6 +129,8 @@ void PromptForKeyPress() bool InstallPrerequisites(_In_ bool installWslOptionalComponent); int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options); int ListDistributionsHelper(_In_ ListOptions options); +int MountRemovable(_In_ std::wstring_view commandLine); +int UnmountRemovable(_In_ std::wstring_view commandLine); LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine); DWORD ParseVersionString(_In_ const std::wstring_view& versionString); int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe); @@ -327,12 +330,16 @@ int ImportDistribution(_In_ std::wstring_view commandLine) std::filesystem::path filePath; ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE; DWORD version = LXSS_WSL_VERSION_DEFAULT; + bool portable = false; + bool allowFixed = false; parser.AddPositionalArgument(name, 0); parser.AddPositionalArgument(AbsolutePath(installPath), 1); parser.AddPositionalArgument(filePath, 2); parser.AddArgument(WslVersion(version), WSL_IMPORT_ARG_VERSION); parser.AddArgument(SetFlag{flags}, WSL_IMPORT_ARG_VHD); + parser.AddArgument(portable, WSL_IMPORT_ARG_PORTABLE); + parser.AddArgument(allowFixed, WSL_IMPORT_ARG_ALLOW_FIXED); parser.Parse(); @@ -341,6 +348,36 @@ int ImportDistribution(_In_ std::wstring_view commandLine) THROW_HR(E_INVALIDARG); } + // If portable flag is set, create a portable distribution + if (portable) + { + try + { + wsl::windows::common::portable::CreatePortableDistribution( + *installPath, + name, + filePath, + version, + flags, + allowFixed); + + wsl::windows::common::wslutil::PrintMessage( + Localization::MessagePortableDistroCreated(name, installPath->c_str()), + stdout); + + wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); + return 0; + } + catch (...) + { + const auto hr = wil::ResultFromCaughtException(); + wsl::windows::common::wslutil::PrintMessage( + Localization::MessagePortableDistroCreationFailed(installPath->c_str()), + stderr); + THROW_HR(hr); + } + } + // Ensure that the install path exists. bool directoryCreated = true; if (!CreateDirectoryW(installPath->c_str(), nullptr)) @@ -1260,6 +1297,79 @@ int Unmount(_In_ const std::wstring& arg) return 0; } +int MountRemovable(_In_ std::wstring_view commandLine) +{ + std::filesystem::path portablePath; + std::optional distroName; + bool temporary = false; + bool allowFixed = false; + + ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); + parser.AddPositionalArgument(UnquotedPath(portablePath), 0); + parser.AddArgument(distroName, WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION); + parser.AddArgument(temporary, WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION); + parser.AddArgument(allowFixed, WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION); + parser.Parse(); + + THROW_HR_IF(WSL_E_INVALID_USAGE, portablePath.empty()); + + try + { + auto result = wsl::windows::common::portable::MountPortableDistribution( + portablePath, + distroName.has_value() ? distroName->c_str() : nullptr, + temporary, + allowFixed); + + wsl::windows::common::wslutil::PrintMessage( + Localization::MessagePortableDistroMounted(result.DistroName.c_str(), result.VhdxPath.c_str()), + stdout); + + wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); + return 0; + } + catch (...) + { + const auto hr = wil::ResultFromCaughtException(); + wsl::windows::common::wslutil::PrintMessage( + Localization::MessagePortableDistroMountFailed(portablePath.c_str()), + stderr); + THROW_HR(hr); + } +} + +int UnmountRemovable(_In_ std::wstring_view commandLine) +{ + LPCWSTR distributionName{}; + ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); + parser.AddPositionalArgument(distributionName, 0); + parser.Parse(); + + THROW_HR_IF(WSL_E_INVALID_USAGE, distributionName == nullptr); + + wsl::windows::common::SvcComm service; + const GUID distroGuid = service.GetDistributionId(distributionName); + + // Check if it's a portable distribution + if (!wsl::windows::common::portable::IsPortableDistribution(distroGuid)) + { + wsl::windows::common::wslutil::PrintMessage( + Localization::MessageNotPortableDistro(distributionName), + stderr); + return -1; + } + + // Unmount the portable distribution + wsl::windows::common::portable::UnmountPortableDistribution(distroGuid, true); + + wsl::windows::common::wslutil::PrintMessage( + Localization::MessagePortableDistroUnmounted(distributionName), + stdout); + + wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); + return 0; +} + int UnregisterDistribution(_In_ LPCWSTR distributionName) { auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageStatusUnregistering(), true); @@ -1668,6 +1778,16 @@ int WslMain(_In_ std::wstring_view commandLine) commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return ImportDistributionInplace(commandLine); } + else if (argument == WSL_MOUNT_REMOVABLE_ARG) + { + commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); + return MountRemovable(commandLine); + } + else if (argument == WSL_UNMOUNT_REMOVABLE_ARG) + { + commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); + return UnmountRemovable(commandLine); + } else if ((argument == WSL_LIST_ARG) || (argument == WSL_LIST_ARG_LONG)) { return ListDistributions(commandLine); diff --git a/src/windows/inc/wsl.h b/src/windows/inc/wsl.h index ce4b9e430..2aa74e50d 100644 --- a/src/windows/inc/wsl.h +++ b/src/windows/inc/wsl.h @@ -87,6 +87,16 @@ Module Name: #define WSL_MOUNT_ARG_TYPE_OPTION L't' #define WSL_MOUNT_ARG_OPTIONS_OPTION L'o' #define WSL_MOUNT_ARG_PARTITION_OPTION L'p' +#define WSL_MOUNT_REMOVABLE_ARG L"--mount-removable" +#define WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION_LONG L"--distro" +#define WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION L'd' +#define WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION_LONG L"--temporary" +#define WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION L't' +#define WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION_LONG L"--allow-fixed" +#define WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION L'f' +#define WSL_IMPORT_ARG_PORTABLE L"--portable" +#define WSL_IMPORT_ARG_ALLOW_FIXED L"--allow-fixed" +#define WSL_UNMOUNT_REMOVABLE_ARG L"--unmount-removable" #define WSL_PARENT_CONSOLE_ARG L"--parent-console" #define WSL_SET_DEFAULT_DISTRO_ARG L"-s" #define WSL_SET_DEFAULT_DISTRO_ARG_LEGACY L"--setdefault"