diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 0ffff8fd7a..3df349b4fe 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -456,6 +456,13 @@ Arguments for managing Windows Subsystem for Linux: --from-file <Path> Install a distribution from a local file. + --fs-type <FsType> + Specify the filesystem type to use for the distribution root. + Defaults to ext4. + + --fs-mount-options <Options> + Specify additional mount options for the filesystem. + --legacy Use the legacy distribution manifest. @@ -499,6 +506,9 @@ Arguments for managing Windows Subsystem for Linux: --compact Compact the VHDX file of a WSL 2 distribution. + --set-fs-mount-options <Options> + Set the filesystem mount options for the distribution root. + --mount <Disk> Attaches and mounts a physical or virtual disk in all WSL 2 distributions. @@ -574,6 +584,13 @@ Arguments for managing distributions in Windows Subsystem for Linux: Specifies that the provided file is a .vhd or .vhdx file, not a tar file. This operation makes a copy of the VHD file at the specified install location. + --fs-type <FsType> + Specify the filesystem type to use for the distribution root. + Defaults to ext4. + + --fs-mount-options <Options> + Specify additional mount options for the filesystem. + --import-in-place <Distro> <FileName> Imports the specified VHD file as a new distribution. This virtual hard disk must be formatted with the ext4 filesystem type. @@ -614,11 +631,10 @@ Arguments for managing distributions in Windows Subsystem for Linux: "}{Locked="--debug-shell "}{Locked="--install "}{Locked="--list "}{Locked="--online'"}{Locked="--enable-wsl1 "}{Locked="--fixed-vhd -"}{Locked="--from-file "}{Locked="--legacy +"}{Locked="--from-file "}{Locked="--fs-type "}{Locked="--fs-mount-options "}{Locked="--legacy "}{Locked="--location "}{Locked="--name "}{Locked="--no-distribution "}{Locked="--no-launch,"}{Locked="--version "}{Locked="--vhd-size "}{Locked="--web-download -"}{Locked="--manage "}{Locked="--move "}{Locked="--set-sparse,"}{Locked="--set-default-user "}{Locked="--resize "}{Locked="--compact -"}{Locked="--mount "}{Locked="--vhd +"}{Locked="--manage "}{Locked="--move "}{Locked="--set-sparse,"}{Locked="--set-default-user "}{Locked="--resize "}{Locked="--compact"}{Locked="--set-fs-mount-options "}{Locked="--mount "}{Locked="--vhd "}{Locked="--bare "}{Locked="--name "}{Locked="--type "}{Locked="--options "}{Locked="--partition "}{Locked="--set-default-version "}{Locked="--shutdown "}{Locked="--force @@ -1107,8 +1123,8 @@ Falling back to NAT networking. The distribution failed to start. Error code: {}, failure step: {} {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated - - The distribution failed to start because its virtual disk is corrupted. + + The distribution failed to start because its virtual disk is corrupted, has an unexpected filesystem type, or has invalid mount options. Duplicated config key '{}' in {}:{} (Conflicting key: '{}' in {}:{}) diff --git a/src/linux/init/main.cpp b/src/linux/init/main.cpp index a07cc25850..e7ddc496f5 100644 --- a/src/linux/init/main.cpp +++ b/src/linux/init/main.cpp @@ -66,6 +66,9 @@ Module Name: #define BSDTAR_PATH "/usr/bin/bsdtar" #define BINFMT_REGISTER_STRING BINFMT_INTEROP_REGISTRATION_STRING_VM(LX_INIT_BINFMT_NAME) "\n" +#define BTRFS_CREATE_ARG "create" +#define BTRFS_PATH "/usr/sbin/btrfs" +#define BTRFS_SUBVOLUME_ARG "subvolume" #define BINFMT_PATH PROCFS_PATH "/sys/fs/binfmt_misc" #define CHRONY_CONF_PATH ETC_PATH "/chrony.conf" #define CHRONYD_PATH "/sbin/chronyd" @@ -136,7 +139,8 @@ int EnableInterface(int Socket, const char* Name); int ExportToSocket(const char* Source, int Socket, int ErrorSocket, unsigned int flags); -int FormatDevice(unsigned int Lun); +int FormatDevice(unsigned int Lun, const char* FsType); +int CreateBtrfsSubvolumeOnDevice(unsigned int Lun, const char* MountOptions); std::string GetLunDeviceName(unsigned int Lun); @@ -721,19 +725,23 @@ Return Value: return Result; } -int FormatDevice(unsigned int Lun) +int FormatDevice(unsigned int Lun, const char* FsType) /*++ Routine Description: - This routine formats the specified SCSI device with the ext4 file system. - N.B. The group size was chosen based on the best practices for Linux VHDs: + This routine formats the specified SCSI device with the file system. + + N.B. The ext4 group size was chosen based on the best practices for Linux VHDs: https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/best-practices-for-running-linux-on-hyper-v + N.B. The xfs data section options (-d) are also determined based on the VHD sector size of 1MB, as suggested in the link above. + Arguments: Lun - Supplies the LUN number of the SCSI device. + FsType - The filesystem type to format the device with. Return Value: @@ -747,7 +755,30 @@ try WaitForBlockDevice(DevicePath.c_str()); - std::string CommandLine = std::format("/usr/sbin/mkfs.ext4 -G 4096 '{}'", DevicePath); + if (FsType == nullptr) + { + FsType = "ext4"; + } + + std::string CommandLine; + if (strcmp(FsType, "ext4") == 0) + { + CommandLine = std::format("/usr/sbin/mkfs.ext4 -G 4096 '{}'", DevicePath); + } + else if (strcmp(FsType, "btrfs") == 0) + { + CommandLine = std::format("/usr/sbin/mkfs.btrfs '{}'", DevicePath); + } + else if (strcmp(FsType, "xfs") == 0) + { + CommandLine = std::format("/usr/sbin/mkfs.xfs -s size=4096 -d su=1m,sw=1 '{}'", DevicePath); + } + else + { + LOG_ERROR("Unsupported filesystem type: {}", FsType); + errno = ENOSYS; + return -1; + } if (UtilExecCommandLine(CommandLine.c_str(), nullptr) < 0) { return -1; @@ -757,6 +788,84 @@ try } CATCH_RETURN_ERRNO() +int CreateBtrfsSubvolumeOnDevice(unsigned int Lun, const char* MountOptions) +/*++ + This routine creates a btrfs subvolume on the specified SCSI device. + It is a no-op if there is no subvolume name in the mount options or + the subvolume already exists. + +Arguments: + + Lun - Supplies the LUN number of the SCSI device. + MountOptions - The mount options to use when mounting the device. + The subvolume name is extracted from these options. + +Return Value: + + 0 on success, < 0 on failure. +--*/ +try +{ + if (MountOptions == nullptr) + { + return 0; + } + + std::string_view options(MountOptions); + std::string_view subvolName; + + size_t pos = 0; + while (pos < options.size()) + { + size_t end = options.find(',', pos); + if (end == std::string_view::npos) + { + end = options.size(); + } + + std::string_view option = options.substr(pos, end - pos); + if (option.starts_with("subvol=")) + { + subvolName = option.substr(7); // Skip "subvol=" + break; + } + + pos = end + 1; + } + + if (subvolName.empty()) + { + return 0; + } + + std::string DevicePath = GetLunDevicePath(Lun); + std::string TempMountPath; + THROW_LAST_ERROR_IF(CreateTempDirectory("/tmp", TempMountPath) < 0); + + auto unmountOnExit = wil::scope_exit([&TempMountPath]() { + umount(TempMountPath.c_str()); + rmdir(TempMountPath.c_str()); + }); + + THROW_LAST_ERROR_IF(UtilMount(DevicePath.c_str(), TempMountPath.c_str(), "btrfs", 0, nullptr, c_defaultRetryTimeout) < 0); + + std::string SubvolPath = std::format("{}/{}", TempMountPath, subvolName); + + struct stat st; + if (stat(SubvolPath.c_str(), &st) == 0) + { + return 0; + } + + THROW_LAST_ERROR_IF(errno != ENOENT); + + const char* Argv[] = {BTRFS_PATH, BTRFS_SUBVOLUME_ARG, BTRFS_CREATE_ARG, SubvolPath.c_str(), nullptr}; + THROW_LAST_ERROR_IF(UtilCreateProcessAndWait(Argv[0], Argv) < 0); + + return 0; +} +CATCH_RETURN_ERRNO() + std::string GetLunDeviceName(unsigned int Lun) /*++ @@ -2524,13 +2633,20 @@ void ProcessImportExportMessage(gsl::span Buffer, wsl::shared::Socket ListenSocket = UtilListenVsockAnyPort(&ListenAddress, 2, true); THROW_LAST_ERROR_IF(!ListenSocket); + auto* FsType = wsl::shared::string::FromSpan(Buffer, Message->FsTypeOffset); + auto* MountOptions = wsl::shared::string::FromSpan(Buffer, Message->MountOptionsOffset); + if (Message->Header.MessageType == LxMiniInitMessageImport) { - THROW_LAST_ERROR_IF(FormatDevice(Message->DeviceId) < 0); + THROW_LAST_ERROR_IF(FormatDevice(Message->DeviceId, FsType) < 0); + + if (FsType != nullptr && strcmp(FsType, "btrfs") == 0) + { + // create the subvolume if specified in mount options + THROW_LAST_ERROR_IF(CreateBtrfsSubvolumeOnDevice(Message->DeviceId, MountOptions) < 0); + } } - auto* FsType = wsl::shared::string::FromSpan(Buffer, Message->FsTypeOffset); - auto* MountOptions = wsl::shared::string::FromSpan(Buffer, Message->MountOptionsOffset); THROW_LAST_ERROR_IF(MountDevice(Message->MountDeviceType, Message->DeviceId, DISTRO_PATH, FsType, Message->Flags, MountOptions) < 0); Result = 0; diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 3c120c8cec..50515657b4 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -313,12 +313,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; + std::optional fsType; + std::optional fsMountOptions; 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(fsType, WSL_IMPORT_ARG_FS_TYPE); + parser.AddArgument(fsMountOptions, WSL_IMPORT_ARG_FS_MOUNT_OPTIONS); parser.Parse(); @@ -379,7 +383,15 @@ int ImportDistribution(_In_ std::wstring_view commandLine) { wsl::windows::common::HandleConsoleProgressBar progressBar(fileHandle, Localization::MessageImportProgress()); wsl::windows::common::SvcComm service; - service.RegisterDistribution(name, version, fileHandle, installPath->c_str(), flags); + service.RegisterDistribution( + name, + version, + fileHandle, + installPath->c_str(), + flags, + std::nullopt, + fsType.has_value() ? fsType->c_str() : nullptr, + fsMountOptions.has_value() ? fsMountOptions->c_str() : nullptr); } directory_cleanup.release(); @@ -452,6 +464,8 @@ int Install(_In_ std::wstring_view commandLine) bool noDistribution = false; bool legacy = false; bool webDownload = IsWindowsServer(); + std::optional fsType; + std::optional fsMountOptions; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddPositionalArgument(distroArgument, 0); @@ -469,6 +483,8 @@ int Install(_In_ std::wstring_view commandLine) parser.AddArgument(g_promptBeforeExit, WSL_INSTALL_ARG_PROMPT_BEFORE_EXIT_OPTION); parser.AddArgument(SizeString(vhdSize), WSL_INSTALL_ARG_VHD_SIZE); parser.AddArgument(fixedVhd, WSL_INSTALL_ARG_FIXED_VHD); + parser.AddArgument(fsType, WSL_INSTALL_ARG_FS_TYPE); + parser.AddArgument(fsMountOptions, WSL_INSTALL_ARG_FS_MOUNT_OPTIONS); parser.Parse(); @@ -525,7 +541,9 @@ int Install(_In_ std::wstring_view commandLine) file, location.has_value() ? location->c_str() : nullptr, fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0, - vhdSize); + vhdSize, + fsType.has_value() ? fsType->c_str() : nullptr, + fsMountOptions.has_value() ? fsMountOptions->c_str() : nullptr); wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installedName.get()), stdout); @@ -551,7 +569,7 @@ int Install(_In_ std::wstring_view commandLine) if (!noDistribution && (legacy || !rebootRequired)) { auto result = WslInstall::InstallDistribution( - installResult, distroArgument, version, !noLaunchAfterInstall, webDownload, legacy, fixedVhd, name, location, vhdSize); + installResult, distroArgument, version, !noLaunchAfterInstall, webDownload, legacy, fixedVhd, name, location, vhdSize, fsType, fsMountOptions); std::optional flavor; if (installResult.Distribution.has_value()) @@ -893,6 +911,7 @@ int Manage(_In_ std::wstring_view commandLine) std::optional defaultUser; std::optional resize; bool compact = false; + std::optional fsMountOptions; bool allowUnsafe = false; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME, 0); @@ -902,6 +921,7 @@ int Manage(_In_ std::wstring_view commandLine) parser.AddArgument(defaultUser, WSL_MANAGE_ARG_SET_DEFAULT_USER_OPTION_LONG); parser.AddArgument(SizeString(resize), WSL_MANAGE_ARG_RESIZE_OPTION_LONG, WSL_MANAGE_ARG_RESIZE_OPTION); parser.AddArgument(compact, WSL_MANAGE_ARG_COMPACT_OPTION_LONG); + parser.AddArgument(fsMountOptions, WSL_MANAGE_ARG_SET_FS_MOUNT_OPTIONS_LONG); parser.AddArgument(allowUnsafe, WSL_MANAGE_ARG_ALLOW_UNSAFE); parser.Parse(); @@ -910,7 +930,7 @@ int Manage(_In_ std::wstring_view commandLine) wsl::windows::common::SvcComm service; auto distroGuid = service.GetDistributionId(distribution); - if (sparse.has_value() + move.has_value() + defaultUser.has_value() + resize.has_value() + compact != 1) + if (sparse.has_value() + move.has_value() + defaultUser.has_value() + resize.has_value() + compact + fsMountOptions.has_value() != 1) { THROW_HR(WSL_E_INVALID_USAGE); } @@ -964,6 +984,10 @@ int Manage(_In_ std::wstring_view commandLine) progress.End(); THROW_IF_FAILED(result); } + else if (fsMountOptions) + { + service.SetFsMountOptions(&distroGuid, fsMountOptions->c_str()); + } wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; diff --git a/src/windows/common/WslInstall.cpp b/src/windows/common/WslInstall.cpp index b8ed5bce21..0e7bb2f3c5 100644 --- a/src/windows/common/WslInstall.cpp +++ b/src/windows/common/WslInstall.cpp @@ -72,7 +72,9 @@ HRESULT WslInstall::InstallDistribution( _In_ bool fixedVhd, _In_ const std::optional& localName, _In_ const std::optional& location, - _In_ const std::optional& vhdSize) + _In_ const std::optional& vhdSize, + _In_ const std::optional& fsType, + _In_ const std::optional& fsMountOptions) try { wsl::windows::common::ExecutionContext context(wsl::windows::common::InstallDistro); @@ -114,7 +116,7 @@ try if (const auto* distro = std::get_if(&*installResult.Distribution)) { std::tie(installResult.Name, installResult.Id) = - InstallModernDistribution(*distro, version, localName, location, vhdSize, fixedVhd); + InstallModernDistribution(*distro, version, localName, location, vhdSize, fixedVhd, fsType, fsMountOptions); installResult.InstalledViaGitHub = true; } @@ -124,7 +126,9 @@ try {localName.has_value(), WSL_INSTALL_ARG_NAME_LONG}, {location.has_value(), WSL_INSTALL_ARG_LOCATION_LONG}, {vhdSize.has_value(), WSL_INSTALL_ARG_VHD_SIZE}, - {fixedVhd, WSL_INSTALL_ARG_FIXED_VHD}}; + {fixedVhd, WSL_INSTALL_ARG_FIXED_VHD}, + {fsType.has_value(), WSL_INSTALL_ARG_FS_TYPE}, + {fsMountOptions.has_value(), WSL_INSTALL_ARG_FS_MOUNT_OPTIONS}}; for (const auto& [condition, argument] : unsupportedArguments) { @@ -257,7 +261,9 @@ std::pair WslInstall::InstallModernDistribution( const std::optional& name, const std::optional& location, const std::optional& vhdSize, - const bool fixedVhd) + const bool fixedVhd, + const std::optional& fsType, + const std::optional& fsMountOptions) { wsl::windows::common::SvcComm service; @@ -307,7 +313,9 @@ std::pair WslInstall::InstallModernDistribution( file.get(), location.has_value() ? location->c_str() : nullptr, fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0, - vhdSize); + vhdSize, + fsType.has_value() ? fsType->c_str() : nullptr, + fsMountOptions.has_value() ? fsMountOptions->c_str() : nullptr); return {installedName.get(), id}; } \ No newline at end of file diff --git a/src/windows/common/WslInstall.h b/src/windows/common/WslInstall.h index 6e141d1d7d..7a1e9c4ecf 100644 --- a/src/windows/common/WslInstall.h +++ b/src/windows/common/WslInstall.h @@ -41,7 +41,9 @@ class WslInstall _In_ bool fixedVhd, _In_ const std::optional& localName, _In_ const std::optional& location, - _In_ const std::optional& vhdSize); + _In_ const std::optional& vhdSize, + _In_ const std::optional& fsType, + _In_ const std::optional& fsMountOptions); static std::pair> CheckForMissingOptionalComponents(_In_ bool requireWslOptionalComponent); @@ -55,5 +57,7 @@ class WslInstall const std::optional& name, const std::optional& location, const std::optional& vhdSize, - const bool fixedVhd); + const bool fixedVhd, + const std::optional& fsType, + const std::optional& fsMountOptions); }; diff --git a/src/windows/common/svccomm.cpp b/src/windows/common/svccomm.cpp index b63491538e..00a5a81b66 100644 --- a/src/windows/common/svccomm.cpp +++ b/src/windows/common/svccomm.cpp @@ -565,6 +565,8 @@ std::pair wsl::windows::common::SvcComm::Reg _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ std::optional VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName) const { ClientExecutionContext context; @@ -590,6 +592,8 @@ std::pair wsl::windows::common::SvcComm::Reg TargetDirectory, Flags, VhdSize.value_or(0), + FsType, + FsMountOptions, PackageFamilyName, &installedName, context.OutError(), @@ -605,6 +609,8 @@ std::pair wsl::windows::common::SvcComm::Reg TargetDirectory, Flags, VhdSize.value_or(0), + FsType, + FsMountOptions, PackageFamilyName, &installedName, context.OutError(), @@ -633,6 +639,13 @@ wsl::windows::common::SvcComm::SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOL Spar RETURN_HR(m_userSession->SetSparse(DistroGuid, Sparse, AllowUnsafe, context.OutError())); } +void wsl::windows::common::SvcComm::SetFsMountOptions(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions) const +{ + ClientExecutionContext context; + + THROW_IF_FAILED(m_userSession->SetFsMountOptions(DistroGuid, FsMountOptions, context.OutError())); +} + HRESULT wsl::windows::common::SvcComm::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ ULONG64 NewSize) const { diff --git a/src/windows/common/svccomm.hpp b/src/windows/common/svccomm.hpp index 47e13d50a6..43046436a0 100644 --- a/src/windows/common/svccomm.hpp +++ b/src/windows/common/svccomm.hpp @@ -84,6 +84,8 @@ class SvcComm _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ std::optional VhdSize = std::nullopt, + _In_opt_ LPCWSTR FsType = nullptr, + _In_opt_ LPCWSTR FsMountOptions = nullptr, _In_opt_ LPCWSTR PackageFamilyName = nullptr) const; HRESULT @@ -97,6 +99,8 @@ class SvcComm HRESULT SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOL Sparse, _In_ BOOL AllowUnsafe) const; + void SetFsMountOptions(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions) const; + HRESULT SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Version) const; diff --git a/src/windows/common/wslutil.cpp b/src/windows/common/wslutil.cpp index 2e914a0696..f2b86b94d6 100644 --- a/src/windows/common/wslutil.cpp +++ b/src/windows/common/wslutil.cpp @@ -84,7 +84,7 @@ static const std::map g_commonErrors{ X(WSL_E_WSL1_DISABLED), X(WSL_E_VIRTUAL_MACHINE_PLATFORM_REQUIRED), X(WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED), - X(WSL_E_DISK_CORRUPTED), + X(WSL_E_MOUNT_FAILED), X(WSL_E_DISTRIBUTION_NAME_NEEDED), X(WSL_E_INVALID_JSON), X(WSL_E_VM_CRASHED), @@ -884,8 +884,8 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result) case WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED: return Localization::MessageLocalSystemNotSupported(); - case WSL_E_DISK_CORRUPTED: - return Localization::MessageDiskCorrupted(); + case WSL_E_MOUNT_FAILED: + return Localization::MessageMountFailed(); case WSL_E_NOT_A_LINUX_DISTRO: return Localization::MessageInvalidDistributionTar(); diff --git a/src/windows/inc/wsl.h b/src/windows/inc/wsl.h index f9bb847f54..5aa3413649 100644 --- a/src/windows/inc/wsl.h +++ b/src/windows/inc/wsl.h @@ -29,6 +29,8 @@ Module Name: #define WSL_EXPORT_ARG_VHD_OPTION L"--vhd" #define WSL_HELP_ARG L"--help" #define WSL_IMPORT_ARG L"--import" +#define WSL_IMPORT_ARG_FS_MOUNT_OPTIONS L"--fs-mount-options" +#define WSL_IMPORT_ARG_FS_TYPE L"--fs-type" #define WSL_IMPORT_ARG_STDIN L"-" #define WSL_IMPORT_ARG_VERSION L"--version" #define WSL_IMPORT_ARG_VHD L"--vhd" @@ -40,6 +42,8 @@ Module Name: #define WSL_INSTALL_ARG_FIXED_VHD L"--fixed-vhd" #define WSL_INSTALL_ARG_FROM_FILE_OPTION L'f' #define WSL_INSTALL_ARG_FROM_FILE_LONG L"--from-file" +#define WSL_INSTALL_ARG_FS_MOUNT_OPTIONS L"--fs-mount-options" +#define WSL_INSTALL_ARG_FS_TYPE L"--fs-type" #define WSL_INSTALL_ARG_LEGACY_LONG L"--legacy" #define WSL_INSTALL_ARG_LOCATION_OPTION L'l' #define WSL_INSTALL_ARG_LOCATION_LONG L"--location" @@ -76,6 +80,7 @@ Module Name: #define WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG L"--set-sparse" #define WSL_MANAGE_ARG_SET_DEFAULT_USER_OPTION_LONG L"--set-default-user" #define WSL_MANAGE_ARG_COMPACT_OPTION_LONG L"--compact" +#define WSL_MANAGE_ARG_SET_FS_MOUNT_OPTIONS_LONG L"--set-fs-mount-options" #define WSL_MOUNT_ARG L"--mount" #define WSL_MOUNT_ARG_VHD_OPTION_LONG L"--vhd" #define WSL_MOUNT_ARG_BARE_OPTION_LONG L"--bare" diff --git a/src/windows/service/exe/DistributionRegistration.cpp b/src/windows/service/exe/DistributionRegistration.cpp index 03cdc26f09..1103331969 100644 --- a/src/windows/service/exe/DistributionRegistration.cpp +++ b/src/windows/service/exe/DistributionRegistration.cpp @@ -57,7 +57,18 @@ DistributionRegistration DistributionRegistration::Open(HKEY LxssKey, const GUID } DistributionRegistration DistributionRegistration::Create( - HKEY LxssKey, const std::optional& Id, LPCWSTR Name, ULONG Version, LPCWSTR BasePath, ULONG Flags, ULONG DefaultUID, LPCWSTR PackageFamilyName, LPCWSTR VhdFileName, bool EnableOobe) + HKEY LxssKey, + const std::optional& Id, + LPCWSTR Name, + ULONG Version, + LPCWSTR BasePath, + ULONG Flags, + ULONG DefaultUID, + LPCWSTR PackageFamilyName, + LPCWSTR VhdFileName, + LPCWSTR FsType, + LPCWSTR FsMountOptions, + bool EnableOobe) { std::wstring distroGuidString; GUID distroId{}; @@ -111,6 +122,26 @@ DistributionRegistration DistributionRegistration::Create( distribution.Write(Property::VhdFileName, VhdFileName); } + if (ARGUMENT_PRESENT(FsType)) + { + distribution.Write(Property::FsType, FsType); + } + + if (ARGUMENT_PRESENT(FsMountOptions)) + { + distribution.Write(Property::FsMountOptions, FsMountOptions); + } + else if (ARGUMENT_PRESENT(FsType) && wcscmp(FsType, LXSS_DISTRO_DEFAULT_FS_TYPE) != 0) + { + // FsType-specific default mount options + if (wcscmp(FsType, L"ext4") == 0) + distribution.Write(Property::FsMountOptions, LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_EXT4); + else if (wcscmp(FsType, L"btrfs") == 0) + distribution.Write(Property::FsMountOptions, LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_BTRFS); + else if (wcscmp(FsType, L"xfs") == 0) + distribution.Write(Property::FsMountOptions, LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_XFS); + } + // Dismiss the scope exit member so the key is persisted. cleanup.release(); return distribution; diff --git a/src/windows/service/exe/DistributionRegistration.h b/src/windows/service/exe/DistributionRegistration.h index 1da0848a91..eadbbe2e9d 100644 --- a/src/windows/service/exe/DistributionRegistration.h +++ b/src/windows/service/exe/DistributionRegistration.h @@ -55,6 +55,8 @@ class DistributionRegistration ULONG DefaultUID, LPCWSTR PackageFamilyName, LPCWSTR VhdFileName, + LPCWSTR FsType, + LPCWSTR FsMountOptions, bool EnableOobe); static DistributionRegistration Open(HKEY LxssKey, const GUID& Id); @@ -91,6 +93,8 @@ namespace Property { inline DistributionPropertyWithDefault PackageFamilyName{L"PackageFamilyName", L""}; inline DistributionPropertyWithDefault KernelCommandLine{L"KernelCommandLine", L""}; inline DistributionPropertyWithDefault VhdFileName{L"VhdFileName", LXSS_VM_MODE_VHD_NAME}; + inline DistributionPropertyWithDefault FsType{L"FsType", LXSS_DISTRO_DEFAULT_FS_TYPE}; + inline DistributionPropertyWithDefault FsMountOptions{L"FsMountOptions", LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS}; inline ExpectedProperty Name{L"DistributionName"}; inline ExpectedProperty BasePath{L"BasePath"}; inline DistributionProperty Flavor{L"Flavor"}; diff --git a/src/windows/service/exe/LxssCreateProcess.h b/src/windows/service/exe/LxssCreateProcess.h index 8471e7b408..365ae76738 100644 --- a/src/windows/service/exe/LxssCreateProcess.h +++ b/src/windows/service/exe/LxssCreateProcess.h @@ -110,6 +110,8 @@ typedef struct _LXSS_DISTRO_CONFIGURATION std::filesystem::path BasePath; std::wstring PackageFamilyName; std::filesystem::path VhdFilePath; + std::wstring FsType; + std::wstring FsMountOptions; ULONG Flags; std::wstring Flavor; std::wstring OsVersion; diff --git a/src/windows/service/exe/LxssUserSession.cpp b/src/windows/service/exe/LxssUserSession.cpp index 866c26614e..a8562e71d5 100644 --- a/src/windows/service/exe/LxssUserSession.cpp +++ b/src/windows/service/exe/LxssUserSession.cpp @@ -383,6 +383,8 @@ HRESULT STDMETHODCALLTYPE LxssUserSession::RegisterDistribution( _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_ LPWSTR* InstalledDistributionName, _Out_ LXSS_ERROR_INFO* Error, @@ -395,7 +397,7 @@ try RETURN_HR_IF(RPC_E_DISCONNECTED, !session); return session->RegisterDistribution( - DistributionName, Version, FileHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, PackageFamilyName, InstalledDistributionName, pDistroGuid); + DistributionName, Version, FileHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, FsType, FsMountOptions, PackageFamilyName, InstalledDistributionName, pDistroGuid); } CATCH_RETURN() @@ -415,6 +417,8 @@ try TargetDirectory, 0, 0, + nullptr, + nullptr, packageFamilyName.empty() ? nullptr : packageFamilyName.c_str(), nullptr, nullptr, @@ -430,6 +434,8 @@ HRESULT STDMETHODCALLTYPE LxssUserSession::RegisterDistributionPipe( _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_ LPWSTR* InstalledDistributionName, _Out_ LXSS_ERROR_INFO* Error, @@ -442,7 +448,7 @@ try RETURN_HR_IF(RPC_E_DISCONNECTED, !session); return session->RegisterDistribution( - DistributionName, Version, PipeHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, PackageFamilyName, InstalledDistributionName, pDistroGuid); + DistributionName, Version, PipeHandle, ErrorHandle, TargetDirectory, Flags, VhdSize, FsType, FsMountOptions, PackageFamilyName, InstalledDistributionName, pDistroGuid); } CATCH_RETURN() @@ -480,6 +486,18 @@ try } CATCH_RETURN() +HRESULT STDMETHODCALLTYPE LxssUserSession::SetFsMountOptions(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions, _Out_ LXSS_ERROR_INFO* Error) +try +{ + ServiceExecutionContext context(Error); + + const auto session = m_session.lock(); + RETURN_HR_IF(RPC_E_DISCONNECTED, !session); + + return session->SetFsMountOptions(DistroGuid, FsMountOptions); +} +CATCH_RETURN() + HRESULT STDMETHODCALLTYPE LxssUserSession::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ HANDLE OutputHandle, _In_ ULONG64 NewSize, _Out_ LXSS_ERROR_INFO* Error) try { @@ -1329,6 +1347,8 @@ LxssUserSessionImpl::ImportDistributionInplace(_In_ LPCWSTR DistributionName, _I LX_UID_ROOT, nullptr, path.filename().c_str(), + nullptr, + nullptr, false); auto configuration = s_GetDistributionConfiguration(registration); @@ -1381,6 +1401,8 @@ HRESULT LxssUserSessionImpl::RegisterDistribution( _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_opt_ LPWSTR* InstalledDistributionName, _Out_ GUID* pDistroGuid) @@ -1519,6 +1541,8 @@ HRESULT LxssUserSessionImpl::RegisterDistribution( LX_UID_ROOT, PackageFamilyName, vhdName.c_str(), + FsType, + FsMountOptions, WI_IsFlagClear(Flags, LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE)); configuration = s_GetDistributionConfiguration(registration, DistributionName == nullptr); @@ -1793,6 +1817,30 @@ try } CATCH_RETURN() +HRESULT LxssUserSessionImpl::SetFsMountOptions(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions) +try +{ + ExecutionContext context(Context::ConfigureDistro); + + WSL_LOG("SetFsMountOptions", TraceLoggingValue(FsMountOptions, "FsMountOptions")); + + const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); + const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get()); + std::lock_guard lock(m_instanceLock); + + // Ensure the distribution exists. + auto distribution = DistributionRegistration::Open(lxssKey.get(), *DistroGuid); + + // Write the new mount options. + distribution.Write(Property::FsMountOptions, FsMountOptions); + + // Terminate the distribution so the new settings will take effect. + _TerminateInstanceInternal(&distribution.Id(), false); + + return S_OK; +} +CATCH_RETURN() + HRESULT LxssUserSessionImpl::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ HANDLE OutputHandle, _In_ ULONG64 NewSize) try { @@ -2535,7 +2583,7 @@ void LxssUserSessionImpl::_CreateLegacyRegistration(_In_ HKEY LxssKey, _In_ HAND const auto basePath = wsl::windows::common::filesystem::GetLegacyBasePath(UserToken); DistributionRegistration::Create( - LxssKey, LXSS_LEGACY_DISTRO_GUID, LXSS_LEGACY_INSTALL_NAME, LXSS_DISTRO_VERSION_LEGACY, basePath.c_str(), configFlags, defaultUid, nullptr, LXSS_VM_MODE_VHD_NAME, false); + LxssKey, LXSS_LEGACY_DISTRO_GUID, LXSS_LEGACY_INSTALL_NAME, LXSS_DISTRO_VERSION_LEGACY, basePath.c_str(), configFlags, defaultUid, nullptr, LXSS_VM_MODE_VHD_NAME, nullptr, nullptr, false); _SetDistributionInstalled(LxssKey, LXSS_LEGACY_DISTRO_GUID); } @@ -4122,6 +4170,8 @@ LxssUserSessionImpl::s_GetDistributionConfiguration(const DistributionRegistrati // Read the vhd file name and append to the base path. configuration.VhdFilePath = configuration.BasePath / Distro.Read(Property::VhdFileName); + configuration.FsType = Distro.Read(Property::FsType); + configuration.FsMountOptions = Distro.Read(Property::FsMountOptions); configuration.Flags = Distro.Read(Property::Flags); configuration.OsVersion = Distro.Read(Property::OsVersion).value_or(L""); diff --git a/src/windows/service/exe/LxssUserSession.h b/src/windows/service/exe/LxssUserSession.h index b97e7d5114..12aa0d9a42 100644 --- a/src/windows/service/exe/LxssUserSession.h +++ b/src/windows/service/exe/LxssUserSession.h @@ -164,6 +164,8 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce7e") LxssUserSession _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_ LPWSTR* InstalledDistributionName, _Out_ LXSS_ERROR_INFO* Error, @@ -180,6 +182,8 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce7e") LxssUserSession _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_ LPWSTR* InstalledDistributionName, _Out_ LXSS_ERROR_INFO* Error, @@ -205,6 +209,11 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce7e") LxssUserSession /// IFACEMETHOD(SetSparse)(_In_ LPCGUID DistroGuid, _In_ BOOLEAN Sparse, _In_ BOOLEAN AllowUnsafe, _Out_ LXSS_ERROR_INFO* Error) override; + /// + /// Sets the filesystem mount options for a distribution. + /// + IFACEMETHOD(SetFsMountOptions)(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions, _Out_ LXSS_ERROR_INFO* Error) override; + /// /// Sets the version for a distribution. /// @@ -462,6 +471,8 @@ class LxssUserSessionImpl _In_ LPCWSTR TargetDirectory, _In_ ULONG Flags, _In_ ULONG64 VhdSize, + _In_opt_ LPCWSTR FsType, + _In_opt_ LPCWSTR FsMountOptions, _In_opt_ LPCWSTR PackageFamilyName, _Out_opt_ LPWSTR* InstalledDistributionName, _Out_ GUID* pDistroGuid); @@ -490,6 +501,12 @@ class LxssUserSessionImpl HRESULT SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOLEAN Sparse, _In_ BOOLEAN AllowUnsafe); + /// + /// Sets the filesystem mount options for a distribution. + /// + HRESULT + SetFsMountOptions(_In_ LPCGUID DistroGuid, _In_ LPCWSTR FsMountOptions); + /// /// Sets the version for a distribution. /// diff --git a/src/windows/service/exe/WslCoreInstance.cpp b/src/windows/service/exe/WslCoreInstance.cpp index 85387ed146..7dd31498a8 100644 --- a/src/windows/service/exe/WslCoreInstance.cpp +++ b/src/windows/service/exe/WslCoreInstance.cpp @@ -68,7 +68,7 @@ WslCoreInstance::WslCoreInstance( // N.B. EUCLEAN (117) can be returned if the disk's journal is corrupted. if ((result.Result == EINVAL || result.Result == 117) && result.FailureStep == LxInitCreateInstanceStepMountDisk) { - THROW_HR(WSL_E_DISK_CORRUPTED); + THROW_HR(WSL_E_MOUNT_FAILED); } else { diff --git a/src/windows/service/exe/WslCoreVm.cpp b/src/windows/service/exe/WslCoreVm.cpp index 5a1a60b014..0096154289 100644 --- a/src/windows/service/exe/WslCoreVm.cpp +++ b/src/windows/service/exe/WslCoreVm.cpp @@ -1222,8 +1222,8 @@ std::shared_ptr WslCoreVm::CreateInstance( message->MountDeviceType = LxMiniInitMountDeviceTypeLun; message->DeviceId = lun; message->Flags = flags; - message.WriteString(message->FsTypeOffset, "ext4"); - message.WriteString(message->MountOptionsOffset, "discard,errors=remount-ro,data=ordered"); + message.WriteString(message->FsTypeOffset, Configuration.FsType); + message.WriteString(message->MountOptionsOffset, Configuration.FsMountOptions); message.WriteString(message->VmIdOffset, m_machineId); message.WriteString(message->DistributionNameOffset, Configuration.Name); message.WriteString(message->SharedMemoryRootOffset, sharedMemoryRoot); diff --git a/src/windows/service/inc/wslservice.idl b/src/windows/service/inc/wslservice.idl index 07d993996c..c981a834ad 100644 --- a/src/windows/service/inc/wslservice.idl +++ b/src/windows/service/inc/wslservice.idl @@ -102,6 +102,12 @@ cpp_quote("#define LXSS_DISTRO_DEFAULT_ENVIRONMENT \"HOSTTYPE=\" DISTRO_HOSTTYPE cpp_quote("#define LXSS_DISTRO_DEFAULT_KERNEL_COMMAND_LINE \"BOOT_IMAGE=/kernel init=/init\"") +cpp_quote("#define LXSS_DISTRO_DEFAULT_FS_TYPE L\"ext4\"") +cpp_quote("#define LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_EXT4 L\"discard,errors=remount-ro,data=ordered\"") +cpp_quote("#define LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_BTRFS L\"discard\"") +cpp_quote("#define LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_XFS L\"discard\"") +cpp_quote("#define LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS LXSS_DISTRO_DEFAULT_FS_MOUNT_OPTIONS_EXT4") + cpp_quote("#define LXSS_DISTRO_FLAGS_ENABLE_INTEROP 0x1") cpp_quote("#define LXSS_DISTRO_FLAGS_APPEND_NT_PATH 0x2") cpp_quote("#define LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING 0x4") @@ -141,7 +147,7 @@ cpp_quote("#define LXSS_PLAN9_UNIX_SOCKET L\"fsserver\"") cpp_quote("#define LXSS_VM_MODE_INITRD_NAME L\"initrd.img\"") cpp_quote("#define LXSS_VM_MODE_KERNEL_NAME L\"kernel\"") -cpp_quote("#define LXSS_VM_MODE_VHD_NAME L\"ext4.vhdx\"") +cpp_quote("#define LXSS_VM_MODE_VHD_NAME L\"distro.vhdx\"") cpp_quote("#define LXSS_TOOLS_DIRECTORY L\"tools\"") @@ -191,6 +197,8 @@ interface ILxssUserSession : IUnknown [in, unique] LPCWSTR TargetDirectory, [in] ULONG Flags, [in] ULONG64 VhdSize, + [in, unique] LPCWSTR FsType, + [in, unique] LPCWSTR FsMountOptions, [in, unique] LPCWSTR PackageFamilyName, [out] LPWSTR* InstalledDistributionName, [in, out] LXSS_ERROR_INFO* Error, @@ -205,6 +213,8 @@ interface ILxssUserSession : IUnknown [in, unique] LPCWSTR TargetDirectory, [in] ULONG Flags, [in] ULONG64 VhdSize, + [in, unique] LPCWSTR FsType, + [in, unique] LPCWSTR FsMountOptions, [in, unique] LPCWSTR PackageFamilyName, [out] LPWSTR* InstalledDistributionName, [in, out] LXSS_ERROR_INFO* Error, @@ -351,6 +361,11 @@ interface ILxssUserSession : IUnknown HRESULT CompactDistribution( [in] LPCGUID DistroGuid, [in, out] LXSS_ERROR_INFO* Error); + + HRESULT SetFsMountOptions( + [in] LPCGUID DistroGuid, + [in] LPCWSTR FsMountOptions, + [in, out] LXSS_ERROR_INFO* Error); }; @@ -398,7 +413,7 @@ cpp_quote("#define WSL_E_DISK_MOUNT_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILI cpp_quote("#define WSL_E_WSL1_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x2C) /* 0x8004032C */") cpp_quote("#define WSL_E_VIRTUAL_MACHINE_PLATFORM_REQUIRED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x2D) /* 0x8004032D */") cpp_quote("#define WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x2E) /* 0x8004032E */") -cpp_quote("#define WSL_E_DISK_CORRUPTED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x2F) /* 0x8004032F */") +cpp_quote("#define WSL_E_MOUNT_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x2F) /* 0x8004032F */") cpp_quote("#define WSL_E_DISTRIBUTION_NAME_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x30) /* 0x80040330 */") cpp_quote("#define WSL_E_INVALID_JSON MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x31) /* 0x80040331 */") cpp_quote("#define WSL_E_VM_CRASHED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x32) /* 0x80040332 */") diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 035323b75a..02d8afa94d 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -1144,7 +1144,7 @@ class UnitTests { const auto tarFileName = LXSST_IMPORT_DISTRO_TEST_DIR L"test.tar"; const auto rootfsDirectoryName = LXSST_IMPORT_DISTRO_TEST_DIR L"rootfs"; - const auto vhdFileName = LXSST_IMPORT_DISTRO_TEST_DIR L"ext4.vhdx"; + const auto vhdFileName = LXSST_IMPORT_DISTRO_TEST_DIR L"distro.vhdx"; auto cleanup = wil::scope_exit([&] { try { @@ -1278,6 +1278,140 @@ class UnitTests VERIFY_ARE_EQUAL(err, L"bsdtar: Error opening archive: Unrecognized archive format\n"); } + WSL2_TEST_METHOD(ImportDistroWithFsType) + { + // + // Test importing a distribution with custom filesystem type. + // Validates ext4, btrfs, and xfs filesystem types. + // + + auto validateOutput = [](LPCWSTR commandLine, LPCWSTR expectedOutput, DWORD expectedExitCode = 0) { + auto [out, err] = LxsstuLaunchWslAndCaptureOutput(commandLine, expectedExitCode); + VERIFY_ARE_EQUAL(expectedOutput, out); + }; + + auto testFsType = [&](LPCWSTR fsType, LPCWSTR expectedFsType) { + const auto distroName = std::format(L"fstype-test-{}", fsType); + + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + LxsstuLaunchWsl(std::format(L"--unregister {}", distroName)); + std::filesystem::remove_all(LXSST_IMPORT_DISTRO_TEST_DIR); + }); + + // Import with custom fs type + validateOutput( + std::format(L"--import {} {} \"{}\" --version 2 --fs-type {}", distroName, LXSST_IMPORT_DISTRO_TEST_DIR, g_testDistroPath, fsType).c_str(), + L"The operation completed successfully. \r\n", + 0); + + // Verify the filesystem type is correct by checking /etc/fstab or mount output + auto [mountOut, _] = LxsstuLaunchWslAndCaptureOutput(std::format(L"-d {} -- mount | grep ' / '", distroName)); + VERIFY_IS_TRUE(mountOut.find(expectedFsType) != std::wstring::npos); + + // Cleanup + WslShutdown(); + }; + + // Test ext4 (default) + testFsType(L"ext4", L"ext4"); + + // Test btrfs + testFsType(L"btrfs", L"btrfs"); + + // Test xfs + testFsType(L"xfs", L"xfs"); + } + + WSL2_TEST_METHOD(ImportDistroWithFsMountOptions) + { + // + // Test importing a distribution with custom filesystem mount options. + // Covers ext4, btrfs (with subvol), and xfs filesystem types. + // + + auto importAndValidate = [&](LPCWSTR testName, const std::wstring& extraArgs, const std::vector& expectedStrings) { + const auto distroName = std::format(L"fsmount-test-{}", testName); + + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + LxsstuLaunchWsl(std::format(L"--unregister {}", distroName)); + std::filesystem::remove_all(LXSST_IMPORT_DISTRO_TEST_DIR); + }); + + auto [out, err] = LxsstuLaunchWslAndCaptureOutput( + std::format(L"--import {} {} \"{}\" --version 2 {}", distroName, LXSST_IMPORT_DISTRO_TEST_DIR, g_testDistroPath, extraArgs), + 0); + VERIFY_ARE_EQUAL(out, L"The operation completed successfully. \r\n"); + + auto [mountOut, _] = LxsstuLaunchWslAndCaptureOutput(std::format(L"-d {} -- mount | grep ' / '", distroName)); + for (const auto& expected : expectedStrings) + { + VERIFY_IS_TRUE(mountOut.find(expected) != std::wstring::npos); + } + + WslShutdown(); + }; + + // Test ext4 with kernel mount options + importAndValidate(L"ext4", L"--fs-mount-options discard,data=writeback", {L"ext4", L"data=writeback"}); + + // Test btrfs with compress, subvol, and ssd options + importAndValidate(L"btrfs", L"--fs-type btrfs --fs-mount-options compress=zstd,subvol=@,ssd", + {L"btrfs", L"compress=zstd", L"subvol=/@", L"ssd"}); + + // Test xfs with quota options (uquota/gquota are displayed as usrquota/grpquota by mount) + importAndValidate(L"xfs", L"--fs-type xfs --fs-mount-options uquota,gquota", + {L"xfs", L"usrquota", L"grpquota"}); + } + + WSL2_TEST_METHOD(ManageSetFsMountOptions) + { + // + // Test the --manage --set-fs-mount-options command across different filesystem types. + // + + auto testManageMountOptions = [&](LPCWSTR testName, const std::wstring& importExtraArgs, + const std::wstring& mountOptions, const std::vector& expectedStrings) { + const auto distroName = std::format(L"manage-fsmount-{}", testName); + + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + LxsstuLaunchWsl(std::format(L"--unregister {}", distroName)); + std::filesystem::remove_all(LXSST_IMPORT_DISTRO_TEST_DIR); + }); + + // Import a distribution + auto [out, err] = LxsstuLaunchWslAndCaptureOutput( + std::format(L"--import {} {} \"{}\" --version 2 {}", distroName, LXSST_IMPORT_DISTRO_TEST_DIR, g_testDistroPath, importExtraArgs), + 0); + VERIFY_ARE_EQUAL(out, L"The operation completed successfully. \r\n"); + + // Set the mount options using --manage + WslShutdown(); + std::tie(out, err) = LxsstuLaunchWslAndCaptureOutput( + std::format(L"--manage {} --set-fs-mount-options {}", distroName, mountOptions), + 0); + VERIFY_ARE_EQUAL(out, L"The operation completed successfully. \r\n"); + + // Verify the mount options are applied after restart + auto [mountOut, _] = LxsstuLaunchWslAndCaptureOutput(std::format(L"-d {} -- mount | grep ' / '", distroName)); + for (const auto& expected : expectedStrings) + { + VERIFY_IS_TRUE(mountOut.find(expected) != std::wstring::npos); + } + + WslShutdown(); + }; + + // Test changing data options on ext4 + testManageMountOptions(L"ext4", L"--fs-type ext4", L"data=journal", {L"ext4", L"data=journal"}); + + // Test changing compress option on btrfs + testManageMountOptions(L"btrfs", L"--fs-type btrfs", L"compress=lzo", {L"btrfs", L"compress=lzo"}); + + // Test enabling quotas on xfs + testManageMountOptions(L"xfs", L"--fs-type xfs", L"uquota,gquota", {L"xfs", L"usrquota", L"grpquota"}); + } + + TEST_METHOD(AppxDistroDeletion) { // Create a dummy distro registration @@ -1296,7 +1430,7 @@ class UnitTests wsl::windows::common::registry::WriteDword(key.get(), nullptr, L"Flags", LXSS_DISTRO_FLAGS_VM_MODE); // Create a dummy vhd - const auto vhdPath = vhdDir.string() + "\\ext4.vhdx"; + const auto vhdPath = vhdDir.string() + "\\distro.vhdx"; wil::unique_handle vhdHandle(CreateFileA(vhdPath.c_str(), GENERIC_READ, 0, nullptr, CREATE_ALWAYS, 0, nullptr)); VERIFY_IS_TRUE(vhdHandle.is_valid()); @@ -1501,7 +1635,7 @@ class UnitTests ValidateErrorMessage( L"-d DummyBrokenDistro", - L"Failed to attach disk 'C:\\DoesNotExit\\ext4.vhdx' to WSL2: The system cannot find the path " + L"Failed to attach disk 'C:\\DoesNotExit\\distro.vhdx' to WSL2: The system cannot find the path " L"specified. ", L"Wsl/Service/CreateInstance/MountDisk/HCS/ERROR_PATH_NOT_FOUND"); @@ -1574,6 +1708,11 @@ class UnitTests ValidateErrorMessage(L"--manage test_distro --resize foo", L"Invalid size: foo", L"Wsl/E_INVALIDARG"); + ValidateErrorMessage( + L"--manage DoesNotExist --set-fs-mount-options discard", + L"There is no distribution with the supplied name.", + L"Wsl/Service/WSL_E_DISTRO_NOT_FOUND"); + ValidateErrorMessage( L"--install --distribution debian --no-distribution", L"Arguments --no-distribution and --distribution can't be specified at same time.", @@ -1753,6 +1892,13 @@ Arguments for managing Windows Subsystem for Linux: --from-file Install a distribution from a local file. + --fs-type + Specify the filesystem type to use for the distribution root. + Defaults to ext4. + + --fs-mount-options + Specify additional mount options for the filesystem. + --legacy Use the legacy distribution manifest. @@ -1796,6 +1942,9 @@ Arguments for managing Windows Subsystem for Linux: --compact Compact the VHDX file of a WSL 2 distribution. + --set-fs-mount-options + Set the filesystem mount options for the distribution root. + --mount Attaches and mounts a physical or virtual disk in all WSL 2 distributions. @@ -1871,6 +2020,13 @@ Arguments for managing distributions in Windows Subsystem for Linux: Specifies that the provided file is a .vhd or .vhdx file, not a tar file. This operation makes a copy of the VHD file at the specified install location. + --fs-type + Specify the filesystem type to use for the distribution root. + Defaults to ext4. + + --fs-mount-options + Specify additional mount options for the filesystem. + --import-in-place Imports the specified VHD file as a new distribution. This virtual hard disk must be formatted with the ext4 filesystem type. @@ -2704,14 +2860,14 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND // Validate that starting the distribution fails with the correct error code. validateOutput( L"-d BrokenDistro echo ok", - L"The distribution failed to start because its virtual disk is corrupted.\r\n" - L"Error code: Wsl/Service/CreateInstance/WSL_E_DISK_CORRUPTED\r\n"); + L"The distribution failed to start because its virtual disk is corrupted, has an unexpected filesystem type, or has invalid mount options.\r\n" + L"Error code: Wsl/Service/CreateInstance/WSL_E_MOUNT_FAILED\r\n"); // Validate that trying to export the distribution fails with the correct error code. validateOutput( L"--export BrokenDistro dummy.tar", - L"The distribution failed to start because its virtual disk is corrupted.\r\n" - L"Error code: Wsl/Service/WSL_E_DISK_CORRUPTED\r\n"); + L"The distribution failed to start because its virtual disk is corrupted, has an unexpected filesystem type, or has invalid mount options.\r\n" + L"Error code: Wsl/Service/WSL_E_MOUNT_FAILED\r\n"); // Shutdown WSL to force the disk to detach. VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--shutdown"), 0L); @@ -2720,8 +2876,8 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND // Import a corrupted vhd. validateOutput( std::format(L"--import-in-place test-distro-corrupted \"{}\"", vhdPath.wstring()), - L"The distribution failed to start because its virtual disk is corrupted.\r\n" - L"Error code: Wsl/Service/RegisterDistro/WSL_E_DISK_CORRUPTED\r\n"); + L"The distribution failed to start because its virtual disk is corrupted, has an unexpected filesystem type, or has invalid mount options.\r\n" + L"Error code: Wsl/Service/RegisterDistro/WSL_E_MOUNT_FAILED\r\n"); // Ensure the VHD can be deleted to make sure it was properly ejected from the VM. VERIFY_ARE_EQUAL(DeleteFileW(vhdPath.c_str()), TRUE); @@ -3102,7 +3258,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND // Validate that the distribution still starts validateDistro(); - VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\ext4.vhdx", testFolder))); + VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\distro.vhdx", testFolder))); } auto absolutePath = wsl::windows::common::filesystem::GetCanonicalPath(".").wstring(); @@ -3114,7 +3270,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND // Validate that the distribution still starts validateDistro(); - VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\ext4.vhdx", absolutePath))); + VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\distro.vhdx", absolutePath))); } // Try to move the distribution to a folder that's already in use @@ -3132,7 +3288,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND L"Wsl/Service/MoveDistro/ERROR_FILE_EXISTS\r\n"); // Validate that the distribution still starts and that the vhd hasn't moved. validateDistro(); - VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\ext4.vhdx", absolutePath))); + VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\distro.vhdx", absolutePath))); } // Try to move the distribution to an invalid path @@ -3147,7 +3303,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND L"Wsl/Service/MoveDistro/ERROR_INVALID_NAME\r\n"); // Validate that the distribution still starts and that the vhd hasn't moved. validateDistro(); - VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\ext4.vhdx", absolutePath))); + VERIFY_IS_TRUE(std::filesystem::exists(std::format(L"{}\\distro.vhdx", absolutePath))); } } @@ -3581,8 +3737,8 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND // std::pair[0] = Written value, std::pair[1] = Actual/Expected value static const std::vector> filePathsToTest{ - {L"C:\\DoesNotExit\\ext4.vhdx", L"C:\\DoesNotExit\\ext4.vhdx"}, - {L"\\DoesNotExit\\ext4.vhdx", L"\\DoesNotExit\\ext4.vhdx"}, + {L"C:\\DoesNotExit\\distro.vhdx", L"C:\\DoesNotExit\\distro.vhdx"}, + {L"\\DoesNotExit\\distro.vhdx", L"\\DoesNotExit\\distro.vhdx"}, {L"", L""}, }; @@ -3603,7 +3759,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND {L"", L""}, {L"notaport", L""}, {L"-5555", L""}, - {L"C:\\DoesNotExit\\ext4.vhdx", L""}, + {L"C:\\DoesNotExit\\distro.vhdx", L""}, }, }, {