CLI: Mount PR followup & fix two docker parser bugs (#41436)

David Bennett committed Aug 26, 2026 at 10:51 UTC 5e0564f8807de30c9611d38d26a6712ebfdeeae5
12 files changed +162 -229
src/windows/common/MountSpecParsing.cpp
+27 -26
@@ -84,11 +84,16 @@ namespace {
84 FieldDefinition{L"destination", Field::Target, Family::General, false, Support::Supported},
85 FieldDefinition{L"readonly", Field::ReadOnly, Family::General, true, Support::Supported},
86 FieldDefinition{L"ro", Field::ReadOnly, Family::General, true, Support::Supported},
87 + // The WSLC mount transport and Docker request model have no end-to-end consistency setting.
88 FieldDefinition{L"consistency", Field::Consistency, Family::General, false, Support::Unsupported},
89 + // The WSLC mount transport and Docker request model do not carry Docker bind options.
90 + // "enabled" requires no non-default bind behavior and is accepted below.
91 FieldDefinition{L"bind-propagation", Field::BindPropagation, Family::Bind, false, Support::Unsupported},
92 FieldDefinition{L"bind-nonrecursive", Field::BindNonRecursive, Family::Bind, true, Support::Unsupported},
93 FieldDefinition{L"bind-recursive", Field::BindRecursive, Family::Bind, false, Support::ValueDependent},
94 + // The mount pipeline relies on Docker's default volume copy-up behavior and does not carry VolumeOptions.
95 FieldDefinition{L"volume-nocopy", Field::VolumeNoCopy, Family::Volume, true, Support::Unsupported},
96 + // Inline mounts reference volumes by name; the volume creation API owns labels, drivers, and driver options.
97 FieldDefinition{L"volume-label", Field::VolumeLabel, Family::Volume, false, Support::Unsupported},
98 FieldDefinition{L"volume-driver", Field::VolumeDriver, Family::Volume, false, Support::Unsupported},
99 FieldDefinition{L"volume-opt", Field::VolumeOption, Family::Volume, false, Support::Unsupported},
@@ -173,21 +178,14 @@ namespace {
178
179 std::optional<uint32_t> ParseDockerTmpfsMode(const std::wstring& value)
180 {
176 - if (value.empty() || value.front() == L'-')
177 - {
178 - return std::nullopt;
179 - }
180 -
181 - size_t position = value.front() == L'+' ? 1 : 0;
182 - if (position == value.size())
181 + if (value.empty())
182 {
183 return std::nullopt;
184 }
185
186 uint64_t result = 0;
188 - for (; position < value.size(); ++position)
187 + for (const auto digit : value)
188 {
190 - const auto digit = value[position];
189 if (digit < L'0' || digit > L'7')
190 {
191 return std::nullopt;
@@ -253,7 +251,10 @@ Spec ParseDockerMountString(const std::wstring& value)
251 case Family::General:
252 break;
253 case Family::Bind:
256 - mount.HasBindOptions = true;
254 + if (definition->Id != Field::BindRecursive || keyValue.Value != L"enabled")
255 + {
256 + mount.HasBindOptions = true;
257 + }
258 break;
259 case Family::Volume:
260 mount.HasVolumeOptions = true;
@@ -417,15 +418,15 @@ Spec ParseDockerMountString(const std::wstring& value)
418 Type type;
419 if (mount.Type == L"bind")
420 {
420 - type = Type::Bind;
421 + type = WSLCMountTypeBind;
422 }
423 else if (mount.Type == L"volume")
424 {
424 - type = Type::Volume;
425 + type = WSLCMountTypeVolume;
426 }
427 else if (mount.Type == L"tmpfs")
428 {
428 - type = Type::Tmpfs;
429 + type = WSLCMountTypeTmpfs;
430 }
431 else
432 {
@@ -495,15 +496,16 @@ Spec ParseDockerVolumeString(const std::wstring& value)
496 if (IsValidNamedVolumeName(rawSource))
497 {
498 return {
498 - .MountType = Type::Volume,
499 + .MountType = WSLCMountTypeVolume,
500 .Source = rawSource,
501 .Target = WideToMultiByte(target),
502 .ReadOnly = readOnly,
503 };
504 }
505
505 - std::wstring source;
506 - if (FAILED(wil::GetFullPathNameW(rawSource.c_str(), source)))
506 + std::error_code error;
507 + auto source = wsl::windows::common::filesystem::GetCanonicalPath(rawSource, error);
508 + if (error)
509 {
510 ThrowParse(Localization::WSLCCLI_VolumeHostPathInvalid(value, rawSource));
511 }
@@ -514,8 +516,8 @@ Spec ParseDockerVolumeString(const std::wstring& value)
516 }
517
518 return {
517 - .MountType = Type::Bind,
518 - .Source = std::move(source),
519 + .MountType = WSLCMountTypeBind,
520 + .Source = source.wstring(),
521 .Target = WideToMultiByte(target),
522 .ReadOnly = readOnly,
523 .BindSource = BindSourcePolicy::CreateIfMissing,
@@ -529,7 +531,7 @@ Spec ParseDockerTmpfsString(const std::wstring& value)
531 const auto options = colon == std::wstring::npos ? std::wstring_view{} : std::wstring_view{value}.substr(colon + 1);
532
533 return {
532 - .MountType = Type::Tmpfs,
534 + .MountType = WSLCMountTypeTmpfs,
535 .Target = WideToMultiByte(target),
536 .TmpfsOptions = WideToMultiByte(std::wstring{options}),
537 };
@@ -547,7 +549,8 @@ void ValidateMountSpec(const Spec& mount)
549 ThrowValidation(Localization::WSLCCLI_MountTargetAbsoluteError());
550 }
551
550 - if (mount.MountType != Type::Tmpfs && (mount.TmpfsSizeBytes.has_value() || mount.TmpfsMode.has_value() || mount.TmpfsOptions.has_value()))
552 + if (mount.MountType != WSLCMountTypeTmpfs &&
553 + (mount.TmpfsSizeBytes.has_value() || mount.TmpfsMode.has_value() || mount.TmpfsOptions.has_value()))
554 {
555 ThrowValidation(Localization::WSLCCLI_MountTmpfsOptionsTypeError());
556 }
@@ -559,7 +562,7 @@ void ValidateMountSpec(const Spec& mount)
562
563 switch (mount.MountType)
564 {
562 - case Type::Bind:
565 + case WSLCMountTypeBind:
566 if (mount.Source.empty())
567 {
568 ThrowValidation(Localization::WSLCCLI_MountSourceRequiredError());
@@ -571,14 +574,14 @@ void ValidateMountSpec(const Spec& mount)
574 }
575 break;
576
574 - case Type::Volume:
577 + case WSLCMountTypeVolume:
578 if (!mount.Source.empty() && !IsValidNamedVolumeName(mount.Source))
579 {
580 ThrowValidation(Localization::WSLCCLI_MountVolumeSourceInvalidError());
581 }
582 break;
583
581 - case Type::Tmpfs:
584 + case WSLCMountTypeTmpfs:
585 if (!mount.Source.empty())
586 {
587 ThrowValidation(Localization::WSLCCLI_MountTmpfsSourceUnsupportedError());
@@ -610,7 +613,7 @@ void ValidateMountCollection(std::span<const Spec> mounts)
613
614 std::string FormatTmpfsOptions(const Spec& mount)
615 {
613 - WI_ASSERT(mount.MountType == Type::Tmpfs);
616 + WI_ASSERT(mount.MountType == WSLCMountTypeTmpfs);
617
618 if (mount.TmpfsOptions.has_value())
619 {
@@ -636,8 +639,6 @@ std::string FormatTmpfsOptions(const Spec& mount)
639
640 std::string NormalizeDestination(std::string destination)
641 {
639 - std::replace(destination.begin(), destination.end(), '\\', '/');
640 -
642 std::vector<std::string> components;
643 size_t start = 0;
644 while (start <= destination.size())
src/windows/common/MountSpecParsing.h
+3 -9
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16
17 +#include "wslc.h"
18 #include <cstdint>
19 #include <exception>
20 #include <optional>
@@ -24,14 +25,7 @@ Abstract:
25
26 namespace wsl::windows::common::mount {
27
27 -inline constexpr std::string_view c_dockerCliMountGrammarVersion = "25.0.3";
28 -
29 -enum class Type
30 -{
31 - Bind,
32 - Volume,
33 - Tmpfs,
34 -};
28 +using Type = WSLCMountType;
29
30 enum class BindSourcePolicy
31 {
@@ -41,7 +35,7 @@ enum class BindSourcePolicy
35
36 struct Spec
37 {
44 - Type MountType = Type::Volume;
38 + Type MountType = WSLCMountTypeVolume;
39 std::wstring Source;
40 std::string Target;
41 bool ReadOnly = false;
src/windows/common/WSLCContainerLauncher.cpp
+5 -19
@@ -230,7 +230,7 @@ void WSLCContainerLauncher::AddUlimit(const std::string& Name, std::int64_t Soft
230 void wsl::windows::common::WSLCContainerLauncher::AddVolume(const std::wstring& HostPath, const std::string& ContainerPath, bool ReadOnly)
231 {
232 AddMount({
233 - .MountType = mount::Type::Bind,
233 + .MountType = WSLCMountTypeBind,
234 .Source = HostPath,
235 .Target = ContainerPath,
236 .ReadOnly = ReadOnly,
@@ -241,7 +241,7 @@ void wsl::windows::common::WSLCContainerLauncher::AddVolume(const std::wstring&
241 void wsl::windows::common::WSLCContainerLauncher::AddNamedVolume(const std::string& Name, const std::string& ContainerPath, bool ReadOnly)
242 {
243 AddMount({
244 - .MountType = mount::Type::Volume,
244 + .MountType = WSLCMountTypeVolume,
245 .Source = wsl::shared::string::MultiByteToWide(Name),
246 .Target = ContainerPath,
247 .ReadOnly = ReadOnly,
@@ -251,20 +251,7 @@ void wsl::windows::common::WSLCContainerLauncher::AddNamedVolume(const std::stri
251 void wsl::windows::common::WSLCContainerLauncher::AddMount(const mount::Spec& Mount)
252 {
253 WSLCMountSpec mount{};
254 - switch (Mount.MountType)
255 - {
256 - case mount::Type::Bind:
257 - mount.Type = WSLCMountTypeBind;
258 - break;
259 -
260 - case mount::Type::Volume:
261 - mount.Type = WSLCMountTypeVolume;
262 - break;
263 -
264 - case mount::Type::Tmpfs:
265 - mount.Type = WSLCMountTypeTmpfs;
266 - break;
267 - }
254 + mount.Type = Mount.MountType;
255
256 if (!Mount.Source.empty())
257 {
@@ -273,7 +260,7 @@ void wsl::windows::common::WSLCContainerLauncher::AddMount(const mount::Spec& Mo
260
261 mount.Target = m_mountTargets.emplace_back(Mount.Target).c_str();
262 mount.ReadOnly = Mount.ReadOnly ? TRUE : FALSE;
276 - if (Mount.MountType == mount::Type::Bind && Mount.BindSource == mount::BindSourcePolicy::CreateIfMissing)
263 + if (Mount.MountType == WSLCMountTypeBind && Mount.BindSource == mount::BindSourcePolicy::CreateIfMissing)
264 {
265 WI_SetFlag(mount.Flags, WSLCMountSpecFlagsCreateSourceIfMissing);
266 }
@@ -292,7 +279,6 @@ void wsl::windows::common::WSLCContainerLauncher::AddMount(const mount::Spec& Mo
279
280 if (Mount.TmpfsOptions.has_value())
281 {
295 - WI_SetFlag(mount.Flags, WSLCMountSpecFlagsTmpfsOptions);
282 mount.TmpfsOptions = m_mountTmpfsOptions.emplace_back(Mount.TmpfsOptions.value()).c_str();
283 }
284
@@ -315,7 +301,7 @@ void wsl::windows::common::WSLCContainerLauncher::AddLabel(const std::string& Ke
301 void wsl::windows::common::WSLCContainerLauncher::AddTmpfs(const std::string& ContainerPath, const std::string& Options)
302 {
303 AddMount({
318 - .MountType = mount::Type::Tmpfs,
304 + .MountType = WSLCMountTypeTmpfs,
305 .Target = ContainerPath,
306 .TmpfsOptions = Options,
307 });
src/windows/service/inc/wslc.idl
+1 -2
@@ -261,10 +261,9 @@ typedef enum _WSLCMountSpecFlags
261 WSLCMountSpecFlagsTmpfsSize = 1,
262 WSLCMountSpecFlagsTmpfsMode = 2,
263 WSLCMountSpecFlagsCreateSourceIfMissing = 4,
264 - WSLCMountSpecFlagsTmpfsOptions = 8,
264 } WSLCMountSpecFlags;
265
267 -cpp_quote("#define WSLCMountSpecFlagsValid (WSLCMountSpecFlagsTmpfsSize | WSLCMountSpecFlagsTmpfsMode | WSLCMountSpecFlagsCreateSourceIfMissing | WSLCMountSpecFlagsTmpfsOptions)")
266 +cpp_quote("#define WSLCMountSpecFlagsValid (WSLCMountSpecFlagsTmpfsSize | WSLCMountSpecFlagsTmpfsMode | WSLCMountSpecFlagsCreateSourceIfMissing)")
267 cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCMountSpecFlags);")
268
269 typedef struct _WSLCMountSpec
src/windows/wslc/services/ContainerModel.cpp
+1 -18
@@ -176,7 +176,7 @@ VolumeMount VolumeMount::Parse(const std::wstring& value)
176 volume.m_host = std::move(mountSpec.Source);
177 volume.m_containerPath = std::move(mountSpec.Target);
178 volume.m_isReadOnlyMode = mountSpec.ReadOnly;
179 - volume.m_isNamedVolume = mountSpec.MountType == mount::Type::Volume;
179 + volume.m_isNamedVolume = mountSpec.MountType == WSLCMountTypeVolume;
180 return volume;
181 }
182
@@ -258,23 +258,6 @@ std::vector<std::wstring> EnvironmentVariable::ParseFile(const std::wstring& fil
258 return envVars;
259 }
260
261 -void ValidateUniqueMountDestinations(const ContainerOptions& options)
262 -{
263 - try
264 - {
265 - mount::ValidateMountCollection(options.Mounts);
266 - }
267 - catch (const mount::MountException& ex)
268 - {
269 - if (ex.Error() == mount::ValidationError::DuplicateDestination)
270 - {
271 - THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::WSLCCLI_DuplicateMountDestinationError(MultiByteToWide(ex.Destination())));
272 - }
273 -
274 - throw;
275 - }
276 -}
277 -
261 CidFile::CidFile(const std::optional<std::wstring>& path)
262 {
263 if (!path.has_value())
src/windows/wslc/services/ContainerModel.h
-2
@@ -311,8 +311,6 @@ private:
311 bool m_isNamedVolume = false;
312 };
313
314 -void ValidateUniqueMountDestinations(const ContainerOptions& options);
315 -
314 class CidFile
315 {
316 public:
src/windows/wslc/tasks/ContainerTasks.cpp
-2
@@ -851,8 +851,6 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
851 options.Mounts.insert(options.Mounts.end(), std::make_move_iterator(tmpfs.begin()), std::make_move_iterator(tmpfs.end()));
852 }
853
854 - ValidateUniqueMountDestinations(options);
855 -
854 for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
855 {
856 options.Labels.push_back(label);
src/windows/wslcsession/WSLCContainer.cpp
+27 -54
@@ -430,15 +430,14 @@ auto MountVolumes(std::vector<WSLCVolumeMount>& volumes, WSLCVirtualMachine& par
430 const auto sourceExists = std::filesystem::exists(volume.HostPath, error);
431 if (error)
432 {
433 - throw wsl::windows::common::mount::MountValidationException(
434 - Localization::MessageWslcBindSourcePathError(volume.HostPath, error.message()));
433 + THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageWslcBindSourcePathError(volume.HostPath, error.message()));
434 }
435
436 if (!sourceExists)
437 {
438 if (!volume.CreateSourceIfMissing)
439 {
441 - throw wsl::windows::common::mount::MountValidationException(Localization::MessageWslcBindSourcePathNotFound(volume.HostPath));
440 + THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageWslcBindSourcePathNotFound(volume.HostPath));
441 }
442
443 auto result = wil::CreateDirectoryDeepNoThrow(volume.HostPath.c_str());
@@ -457,18 +456,6 @@ auto MountVolumes(std::vector<WSLCVolumeMount>& volumes, WSLCVirtualMachine& par
456 return std::move(errorCleanup);
457 }
458
460 -auto MountVolumesWithUserError(std::vector<WSLCVolumeMount>& volumes, WSLCVirtualMachine& parentVM)
461 -{
462 - try
463 - {
464 - return MountVolumes(volumes, parentVM);
465 - }
466 - catch (const wsl::windows::common::mount::MountValidationException& ex)
467 - {
468 - THROW_HR_WITH_USER_ERROR(E_INVALIDARG, ex.Reason());
469 - }
470 -}
471 -
459 WSLCContainerState DockerStateToWSLCState(ContainerState state)
460 {
461 // TODO: Handle other states like Paused, Restarting, etc.
@@ -546,6 +533,9 @@ std::map<std::string, std::string> StripInternalLabels(std::optional<std::map<st
533 return StripInternalLabels(std::move(labels).value_or(std::map<std::string, std::string>{}));
534 }
535
536 +// Validate every mount representation as one collection before preparing VM shares or calling Docker.
537 +// Docker handles duplicate destinations differently across Binds, Mounts, and Tmpfs and can create named volumes while processing the request.
538 +// This service-boundary check gives every caller consistent duplicate semantics and keeps invalid requests side-effect free.
539 std::vector<wsl::windows::common::mount::Spec> ConvertAndValidateMounts(const WSLCContainerOptions& containerOptions)
540 {
541 namespace mount = wsl::windows::common::mount;
@@ -565,39 +555,31 @@ std::vector<wsl::windows::common::mount::Spec> ConvertAndValidateMounts(const WS
555 i,
556 value.Flags);
557
568 - mount::Type type;
558 switch (value.Type)
559 {
560 case WSLCMountTypeBind:
572 - type = mount::Type::Bind;
573 - break;
574 -
561 case WSLCMountTypeVolume:
576 - type = mount::Type::Volume;
577 - break;
578 -
562 case WSLCMountTypeTmpfs:
580 - type = mount::Type::Tmpfs;
563 break;
564
565 default:
566 THROW_HR_MSG(E_INVALIDARG, "Mount at index %lu has invalid type: %d", i, value.Type);
567 }
568
569 + const auto type = value.Type;
570 THROW_HR_IF_MSG(
571 E_INVALIDARG,
589 - type != mount::Type::Bind && WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsCreateSourceIfMissing),
572 + type != WSLCMountTypeBind && WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsCreateSourceIfMissing),
573 "Mount at index %lu specifies create-source-if-missing for a non-bind mount",
574 i);
575 THROW_HR_IF_MSG(
576 E_INVALIDARG,
594 - type != mount::Type::Tmpfs && WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsOptions),
577 + type != WSLCMountTypeTmpfs && value.TmpfsOptions != nullptr,
578 "Mount at index %lu specifies tmpfs options for a non-tmpfs mount",
579 i);
580 THROW_HR_IF_MSG(
581 E_INVALIDARG,
599 - WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsOptions) &&
600 - WI_IsAnyFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsSize | WSLCMountSpecFlagsTmpfsMode),
582 + value.TmpfsOptions != nullptr && WI_IsAnyFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsSize | WSLCMountSpecFlagsTmpfsMode),
583 "Mount at index %lu combines legacy and structured tmpfs options",
584 i);
585
@@ -610,9 +592,7 @@ std::vector<wsl::windows::common::mount::Spec> ConvertAndValidateMounts(const WS
592 : mount::BindSourcePolicy::RequireExisting,
593 .TmpfsSizeBytes = WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsSize) ? std::optional<int64_t>{value.TmpfsSizeBytes} : std::nullopt,
594 .TmpfsMode = WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsMode) ? std::optional<uint32_t>{value.TmpfsMode} : std::nullopt,
613 - .TmpfsOptions = WI_IsFlagSet(value.Flags, WSLCMountSpecFlagsTmpfsOptions)
614 - ? std::optional<std::string>{value.TmpfsOptions != nullptr ? value.TmpfsOptions : ""}
615 - : std::nullopt,
595 + .TmpfsOptions = value.TmpfsOptions != nullptr ? std::optional<std::string>{value.TmpfsOptions} : std::nullopt,
596 });
597 }
598
@@ -621,7 +601,7 @@ std::vector<wsl::windows::common::mount::Spec> ConvertAndValidateMounts(const WS
601 mount::ValidateMountCollection(mounts);
602 for (const auto& mount : mounts)
603 {
624 - if (mount.MountType == mount::Type::Bind)
604 + if (mount.MountType == WSLCMountTypeBind)
605 {
606 if (mount.BindSource == mount::BindSourcePolicy::CreateIfMissing)
607 {
@@ -706,35 +686,28 @@ enum class MissingBindSource
686
687 PreparedBindMount PrepareBindMount(const std::wstring& source, const std::string& target, bool readOnly, MissingBindSource missingSource)
688 {
709 - GUID volumeId;
710 - THROW_IF_FAILED(CoCreateGuid(&volumeId));
711 -
712 - auto parentVMPath = std::format("/mnt/{}", wsl::shared::string::GuidToString<char>(volumeId));
689 std::filesystem::path hostPath = source;
690 THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessagePathNotAbsolute(source), !hostPath.is_absolute());
691
692 std::wstring sourceFilename;
693 {
694 std::error_code ec;
719 - hostPath = std::filesystem::canonical(hostPath, ec);
720 - if (!ec)
695 + hostPath = wsl::windows::common::filesystem::GetCanonicalPath(hostPath, ec);
696 + if (ec)
697 {
722 - if (std::filesystem::is_regular_file(hostPath))
723 - {
724 - sourceFilename = hostPath.filename().wstring();
725 - hostPath = hostPath.parent_path();
726 - }
698 + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ec.value()), Localization::MessageWslcFailedToMountVolume(source, ec.message()));
699 }
728 - else if (ec == std::errc::no_such_file_or_directory)
729 - {
730 - hostPath = source;
731 - }
732 - else
700 +
701 + if (std::filesystem::is_regular_file(hostPath))
702 {
734 - THROW_HR_WITH_USER_ERROR(E_FAIL, Localization::MessageWslcFailedToMountVolume(source, ec.message()));
703 + sourceFilename = hostPath.filename().wstring();
704 + hostPath = hostPath.parent_path();
705 }
706 }
707
708 + GUID volumeId;
709 + THROW_IF_FAILED(CoCreateGuid(&volumeId));
710 + auto parentVMPath = std::format("/mnt/{}", wsl::shared::string::GuidToString<char>(volumeId));
711 auto dockerSource = sourceFilename.empty() ? parentVMPath : std::format("{}/{}", parentVMPath, sourceFilename);
712 return {
713 .Volume =
@@ -1133,7 +1106,7 @@ void WSLCContainerImpl::Start(WSLCContainerStartFlags Flags, const WSLCProcessSt
1106 Localization::MessageWslcVolumeNotAvailable(wsl::shared::string::Join(unavailableVolumes, ',')),
1107 !unavailableVolumes.empty());
1108
1136 - auto volumeCleanup = MountVolumesWithUserError(m_mountedVolumes, m_runtime.Vm());
1109 + auto volumeCleanup = MountVolumes(m_mountedVolumes, m_runtime.Vm());
1110
1111 auto portCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this]() { UnmapPorts(); });
1112 MapPorts();
@@ -2289,7 +2262,7 @@ std::shared_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
2262
2263 switch (mount.MountType)
2264 {
2292 - case wsl::windows::common::mount::Type::Bind:
2265 + case WSLCMountTypeBind:
2266 {
2267 // Docker's colon-delimited bind format cannot represent ':' in the target.
2268 const auto missingSource = mount.BindSource == wsl::windows::common::mount::BindSourcePolicy::CreateIfMissing
@@ -2302,12 +2275,12 @@ std::shared_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
2275 break;
2276 }
2277
2305 - case wsl::windows::common::mount::Type::Volume:
2278 + case WSLCMountTypeVolume:
2279 dockerMount.Source = wsl::shared::string::WideToMultiByte(mount.Source);
2280 dockerMount.Type = "volume";
2281 break;
2282
2310 - case wsl::windows::common::mount::Type::Tmpfs:
2283 + case WSLCMountTypeTmpfs:
2284 if (mount.TmpfsOptions.has_value())
2285 {
2286 request.HostConfig.Tmpfs[mount.Target] = mount.TmpfsOptions.value();
@@ -2470,7 +2443,7 @@ std::shared_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
2443 // Docker validates structured bind sources during container creation, so their VM paths must exist here.
2444 // Release the temporary shares before returning; Start remounts them for the container lifetime.
2445 auto result = [&]() {
2473 - auto volumeCleanup = MountVolumesWithUserError(volumes, virtualMachine);
2446 + auto volumeCleanup = MountVolumes(volumes, virtualMachine);
2447 return DockerClient.CreateContainer(request, containerName);
2448 }();
2449
@@ -2534,7 +2507,7 @@ std::shared_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
2507
2508 for (const auto& mount : mounts)
2509 {
2537 - if (mount.MountType == wsl::windows::common::mount::Type::Volume && !mount.Source.empty())
2510 + if (mount.MountType == WSLCMountTypeVolume && !mount.Source.empty())
2511 {
2512 namedVolumes.emplace_back(wsl::shared::string::WideToMultiByte(mount.Source));
2513 }
test/windows/WSLCTests.cpp
+1 -1
@@ -10317,7 +10317,7 @@ class WSLCTests
10317
10318 VerifyPatternMatch(
10319 wsl::shared::string::WideToMultiByte(comError->Message.get()),
10320 - "Failed to create volume '*test-volume\\subfolder': Access is denied. ");
10320 + "Failed to create volume '*test-volume\\subfolder': Access is denied.");
10321 }
10322
10323 // Validate that files mounts are correctly recovered when a container is loaded from storage
test/windows/wslc/WSLCCLIArgumentUnitTests.cpp
+3 -3
@@ -416,16 +416,16 @@ class WSLCCLIArgumentUnitTests
416 // mount strings -> mount::Spec
417 {
418 const auto volume = ValidateAndGetCached<ArgType::Volume>(LR"(C:\hostPath:/containerPath)");
419 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Bind), static_cast<int>(volume.MountType));
419 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeBind), static_cast<int>(volume.MountType));
420 VERIFY_ARE_EQUAL(static_cast<int>(mount::BindSourcePolicy::CreateIfMissing), static_cast<int>(volume.BindSource));
421
422 const auto tmpfs = ValidateAndGetCached<ArgType::TMPFS>(L"/tmp:size=64k");
423 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Tmpfs), static_cast<int>(tmpfs.MountType));
423 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeTmpfs), static_cast<int>(tmpfs.MountType));
424 VERIFY_IS_TRUE(tmpfs.TmpfsOptions.has_value());
425 VERIFY_ARE_EQUAL(std::string("size=64k"), tmpfs.TmpfsOptions.value());
426
427 const auto structured = ValidateAndGetCached<ArgType::Mount>(L"type=volume,source=data-volume,target=/data");
428 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Volume), static_cast<int>(structured.MountType));
428 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeVolume), static_cast<int>(structured.MountType));
429 VERIFY_ARE_EQUAL(std::wstring(L"data-volume"), structured.Source);
430 }
431
test/windows/wslc/WSLCCLIMountParserUnitTests.cpp
+93 -92
@@ -62,34 +62,34 @@ namespace {
62 };
63
64 constexpr ValidMountCase c_validMountCases[] = {
65 - {L"type=volume,target=/data", mount::Type::Volume, L"", "/data", false, {}, {}, ""},
66 - {L"source=data-volume,target=/data", mount::Type::Volume, L"data-volume", "/data", false, {}, {}, ""},
65 + {L"type=volume,target=/data", WSLCMountTypeVolume, L"", "/data", false, {}, {}, ""},
66 + {L"source=data-volume,target=/data", WSLCMountTypeVolume, L"data-volume", "/data", false, {}, {}, ""},
67 {L"type=volume,source=data-volume,target=/path:voldir",
68 - mount::Type::Volume,
68 + WSLCMountTypeVolume,
69 L"data-volume",
70 "/path:voldir",
71 false,
72 {},
73 {},
74 ""},
75 - {L"TYPE=VOLUME,SOURCE=data-volume,TARGET=/data", mount::Type::Volume, L"data-volume", "/data", false, {}, {}, ""},
76 - {L"type=VoLuMe,source=data-volume,target=/data", mount::Type::Volume, L"data-volume", "/data", false, {}, {}, ""},
77 - {L"type=volume,src=data-volume,dst=/data", mount::Type::Volume, L"data-volume", "/data", false, {}, {}, ""},
78 - {L"type=volume,src=data-volume,destination=/data", mount::Type::Volume, L"data-volume", "/data", false, {}, {}, ""},
79 - {L"type=volume,source=first,source=second,target=/data", mount::Type::Volume, L"second", "/data", false, {}, {}, ""},
75 + {L"TYPE=VOLUME,SOURCE=data-volume,TARGET=/data", WSLCMountTypeVolume, L"data-volume", "/data", false, {}, {}, ""},
76 + {L"type=VoLuMe,source=data-volume,target=/data", WSLCMountTypeVolume, L"data-volume", "/data", false, {}, {}, ""},
77 + {L"type=volume,src=data-volume,dst=/data", WSLCMountTypeVolume, L"data-volume", "/data", false, {}, {}, ""},
78 + {L"type=volume,src=data-volume,destination=/data", WSLCMountTypeVolume, L"data-volume", "/data", false, {}, {}, ""},
79 + {L"type=volume,source=first,source=second,target=/data", WSLCMountTypeVolume, L"second", "/data", false, {}, {}, ""},
80 {L"type=volume,source=data-volume,target=/first,target=/second",
81 - mount::Type::Volume,
81 + WSLCMountTypeVolume,
82 L"data-volume",
83 "/second",
84 false,
85 {},
86 {},
87 ""},
88 - {L"type=volume,type=bind,source=C:\\data,target=/data", mount::Type::Bind, L"C:\\data", "/data", false, {}, {}, ""},
89 - {L"type=volume,source=data-volume,target=/data,readonly", mount::Type::Volume, L"data-volume", "/data", true, {}, {}, ""},
90 - {L"type=volume,source=data-volume,target=/data,ro", mount::Type::Volume, L"data-volume", "/data", true, {}, {}, ""},
88 + {L"type=volume,type=bind,source=C:\\data,target=/data", WSLCMountTypeBind, L"C:\\data", "/data", false, {}, {}, ""},
89 + {L"type=volume,source=data-volume,target=/data,readonly", WSLCMountTypeVolume, L"data-volume", "/data", true, {}, {}, ""},
90 + {L"type=volume,source=data-volume,target=/data,ro", WSLCMountTypeVolume, L"data-volume", "/data", true, {}, {}, ""},
91 {L"type=volume,source=data-volume,target=/data,readonly=1",
92 - mount::Type::Volume,
92 + WSLCMountTypeVolume,
93 L"data-volume",
94 "/data",
95 true,
@@ -97,7 +97,7 @@ namespace {
97 {},
98 ""},
99 {L"type=volume,source=data-volume,target=/data,readonly=t",
100 - mount::Type::Volume,
100 + WSLCMountTypeVolume,
101 L"data-volume",
102 "/data",
103 true,
@@ -105,7 +105,7 @@ namespace {
105 {},
106 ""},
107 {L"type=volume,source=data-volume,target=/data,readonly=T",
108 - mount::Type::Volume,
108 + WSLCMountTypeVolume,
109 L"data-volume",
110 "/data",
111 true,
@@ -113,7 +113,7 @@ namespace {
113 {},
114 ""},
115 {L"type=volume,source=data-volume,target=/data,readonly=TRUE",
116 - mount::Type::Volume,
116 + WSLCMountTypeVolume,
117 L"data-volume",
118 "/data",
119 true,
@@ -121,7 +121,7 @@ namespace {
121 {},
122 ""},
123 {L"type=volume,source=data-volume,target=/data,readonly=true",
124 - mount::Type::Volume,
124 + WSLCMountTypeVolume,
125 L"data-volume",
126 "/data",
127 true,
@@ -129,7 +129,7 @@ namespace {
129 {},
130 ""},
131 {L"type=volume,source=data-volume,target=/data,readonly=True",
132 - mount::Type::Volume,
132 + WSLCMountTypeVolume,
133 L"data-volume",
134 "/data",
135 true,
@@ -137,7 +137,7 @@ namespace {
137 {},
138 ""},
139 {L"type=volume,source=data-volume,target=/data,readonly=0",
140 - mount::Type::Volume,
140 + WSLCMountTypeVolume,
141 L"data-volume",
142 "/data",
143 false,
@@ -145,7 +145,7 @@ namespace {
145 {},
146 ""},
147 {L"type=volume,source=data-volume,target=/data,readonly=f",
148 - mount::Type::Volume,
148 + WSLCMountTypeVolume,
149 L"data-volume",
150 "/data",
151 false,
@@ -153,7 +153,7 @@ namespace {
153 {},
154 ""},
155 {L"type=volume,source=data-volume,target=/data,readonly=F",
156 - mount::Type::Volume,
156 + WSLCMountTypeVolume,
157 L"data-volume",
158 "/data",
159 false,
@@ -161,7 +161,7 @@ namespace {
161 {},
162 ""},
163 {L"type=volume,source=data-volume,target=/data,readonly=FALSE",
164 - mount::Type::Volume,
164 + WSLCMountTypeVolume,
165 L"data-volume",
166 "/data",
167 false,
@@ -169,7 +169,7 @@ namespace {
169 {},
170 ""},
171 {L"type=volume,source=data-volume,target=/data,readonly=false",
172 - mount::Type::Volume,
172 + WSLCMountTypeVolume,
173 L"data-volume",
174 "/data",
175 false,
@@ -177,7 +177,7 @@ namespace {
177 {},
178 ""},
179 {L"type=volume,source=data-volume,target=/data,readonly=False",
180 - mount::Type::Volume,
180 + WSLCMountTypeVolume,
181 L"data-volume",
182 "/data",
183 false,
@@ -185,63 +185,70 @@ namespace {
185 {},
186 ""},
187 {L"type=volume,source=data-volume,target=/data,readonly=true,readonly=false",
188 - mount::Type::Volume,
188 + WSLCMountTypeVolume,
189 L"data-volume",
190 "/data",
191 false,
192 {},
193 {},
194 ""},
195 - {L"type=bind,\"source=C:\\mount,a\",target=/data", mount::Type::Bind, L"C:\\mount,a", "/data", false, {}, {}, ""},
195 + {L"type=volume,source=data-volume,target=/data,bind-recursive=enabled",
196 + WSLCMountTypeVolume,
197 + L"data-volume",
198 + "/data",
199 + false,
200 + {},
201 + {},
202 + ""},
203 + {L"type=bind,\"source=C:\\mount,a\",target=/data", WSLCMountTypeBind, L"C:\\mount,a", "/data", false, {}, {}, ""},
204 {L"type=bind,source=C:\\mount with spaces,target=/data",
197 - mount::Type::Bind,
205 + WSLCMountTypeBind,
206 L"C:\\mount with spaces",
207 "/data",
208 false,
209 {},
210 {},
211 ""},
204 - {L"type=bind,source=C:\\mount,target=/path:mntdir", mount::Type::Bind, L"C:\\mount", "/path:mntdir", false, {}, {}, ""},
205 - {L"type=bind,source=C:\\,target=/data", mount::Type::Bind, L"C:\\", "/data", false, {}, {}, ""},
206 - {L"type=bind,source=\\\\server\\share,target=/data", mount::Type::Bind, L"\\\\server\\share", "/data", false, {}, {}, ""},
212 + {L"type=bind,source=C:\\mount,target=/path:mntdir", WSLCMountTypeBind, L"C:\\mount", "/path:mntdir", false, {}, {}, ""},
213 + {L"type=bind,source=C:\\,target=/data", WSLCMountTypeBind, L"C:\\", "/data", false, {}, {}, ""},
214 + {L"type=bind,source=\\\\server\\share,target=/data", WSLCMountTypeBind, L"\\\\server\\share", "/data", false, {}, {}, ""},
215 {L"type=bind,source=C:\\mount,target=/data,bind-recursive=enabled",
208 - mount::Type::Bind,
216 + WSLCMountTypeBind,
217 L"C:\\mount",
218 "/data",
219 false,
220 {},
221 {},
222 ""},
215 - {L"type=volume,source=A_,target=/data", mount::Type::Volume, L"A_", "/data", false, {}, {}, ""},
216 - {L"type=volume,source=data.volume-1,target=/data", mount::Type::Volume, L"data.volume-1", "/data", false, {}, {}, ""},
217 - {L"type=tmpfs,target=/tmp", mount::Type::Tmpfs, L"", "/tmp", false, {}, {}, ""},
218 - {L"type=tmpfs,target=/path:tmpfs", mount::Type::Tmpfs, L"", "/path:tmpfs", false, {}, {}, ""},
219 - {L"type=tmpfs,target=/tmp,readonly", mount::Type::Tmpfs, L"", "/tmp", true, {}, {}, "ro"},
220 - {L"type=tmpfs,target=/tmp,tmpfs-size=0", mount::Type::Tmpfs, L"", "/tmp", false, 0, {}, ""},
221 - {L"type=tmpfs,target=/tmp,tmpfs-size=1", mount::Type::Tmpfs, L"", "/tmp", false, 1, {}, "size=1"},
222 - {L"type=tmpfs,target=/tmp,tmpfs-size=1024", mount::Type::Tmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
223 - {L"type=tmpfs,target=/tmp,tmpfs-size=1536", mount::Type::Tmpfs, L"", "/tmp", false, 1536, {}, "size=1536"},
224 - {L"type=tmpfs,target=/tmp,tmpfs-size=1k", mount::Type::Tmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
225 - {L"type=tmpfs,target=/tmp,tmpfs-size=1KB", mount::Type::Tmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
226 - {L"type=tmpfs,target=/tmp,tmpfs-size=1KiB", mount::Type::Tmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
227 - {L"type=tmpfs,target=/tmp,tmpfs-size=1MB", mount::Type::Tmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
228 - {L"type=tmpfs,target=/tmp,tmpfs-size=1MiB", mount::Type::Tmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
229 - {L"type=tmpfs,target=/tmp,tmpfs-size=1GB", mount::Type::Tmpfs, L"", "/tmp", false, 1LL << 30, {}, "size=1g"},
230 - {L"type=tmpfs,target=/tmp,tmpfs-size=1.5MB", mount::Type::Tmpfs, L"", "/tmp", false, 1536LL << 10, {}, "size=1536k"},
231 - {L"type=tmpfs,target=/tmp,tmpfs-size=+1MB", mount::Type::Tmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
232 - {L"type=tmpfs,target=/tmp,tmpfs-size=1e3", mount::Type::Tmpfs, L"", "/tmp", false, 1000, {}, "size=1000"},
233 - {L"type=tmpfs,target=/tmp,tmpfs-mode=0000", mount::Type::Tmpfs, L"", "/tmp", false, {}, 0, ""},
234 - {L"type=tmpfs,target=/tmp,tmpfs-mode=0700", mount::Type::Tmpfs, L"", "/tmp", false, {}, 0700, "mode=700"},
235 - {L"type=tmpfs,target=/tmp,tmpfs-mode=+0700", mount::Type::Tmpfs, L"", "/tmp", false, {}, 0700, "mode=700"},
223 + {L"type=volume,source=A_,target=/data", WSLCMountTypeVolume, L"A_", "/data", false, {}, {}, ""},
224 + {L"type=volume,source=data.volume-1,target=/data", WSLCMountTypeVolume, L"data.volume-1", "/data", false, {}, {}, ""},
225 + {L"type=tmpfs,target=/tmp", WSLCMountTypeTmpfs, L"", "/tmp", false, {}, {}, ""},
226 + {L"type=tmpfs,target=/path:tmpfs", WSLCMountTypeTmpfs, L"", "/path:tmpfs", false, {}, {}, ""},
227 + {L"type=tmpfs,target=/tmp,readonly", WSLCMountTypeTmpfs, L"", "/tmp", true, {}, {}, "ro"},
228 + {L"type=tmpfs,target=/tmp,tmpfs-size=0", WSLCMountTypeTmpfs, L"", "/tmp", false, 0, {}, ""},
229 + {L"type=tmpfs,target=/tmp,tmpfs-size=1", WSLCMountTypeTmpfs, L"", "/tmp", false, 1, {}, "size=1"},
230 + {L"type=tmpfs,target=/tmp,tmpfs-size=1024", WSLCMountTypeTmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
231 + {L"type=tmpfs,target=/tmp,tmpfs-size=1536", WSLCMountTypeTmpfs, L"", "/tmp", false, 1536, {}, "size=1536"},
232 + {L"type=tmpfs,target=/tmp,tmpfs-size=1k", WSLCMountTypeTmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
233 + {L"type=tmpfs,target=/tmp,tmpfs-size=1KB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
234 + {L"type=tmpfs,target=/tmp,tmpfs-size=1KiB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1024, {}, "size=1k"},
235 + {L"type=tmpfs,target=/tmp,tmpfs-size=1MB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
236 + {L"type=tmpfs,target=/tmp,tmpfs-size=1MiB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
237 + {L"type=tmpfs,target=/tmp,tmpfs-size=1GB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1LL << 30, {}, "size=1g"},
238 + {L"type=tmpfs,target=/tmp,tmpfs-size=1.5MB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1536LL << 10, {}, "size=1536k"},
239 + {L"type=tmpfs,target=/tmp,tmpfs-size=+1MB", WSLCMountTypeTmpfs, L"", "/tmp", false, 1LL << 20, {}, "size=1m"},
240 + {L"type=tmpfs,target=/tmp,tmpfs-size=1e3", WSLCMountTypeTmpfs, L"", "/tmp", false, 1000, {}, "size=1000"},
241 + {L"type=tmpfs,target=/tmp,tmpfs-mode=0000", WSLCMountTypeTmpfs, L"", "/tmp", false, {}, 0, ""},
242 + {L"type=tmpfs,target=/tmp,tmpfs-mode=0700", WSLCMountTypeTmpfs, L"", "/tmp", false, {}, 0700, "mode=700"},
243 {L"type=tmpfs,target=/tmp,tmpfs-size=1MB,tmpfs-mode=0700,readonly",
237 - mount::Type::Tmpfs,
244 + WSLCMountTypeTmpfs,
245 L"",
246 "/tmp",
247 true,
248 1LL << 20,
249 0700,
250 "ro,mode=700,size=1m"},
244 - {L"type=tmpfs,target=/tmp,tmpfs-size=0,tmpfs-mode=0000,readonly=false", mount::Type::Tmpfs, L"", "/tmp", false, 0, 0, ""},
251 + {L"type=tmpfs,target=/tmp,tmpfs-size=0,tmpfs-mode=0000,readonly=false", WSLCMountTypeTmpfs, L"", "/tmp", false, 0, 0, ""},
252 };
253
254 const InvalidMountCase c_invalidMountCases[] = {
@@ -339,8 +346,6 @@ namespace {
346 Localization::WSLCCLI_MountOptionFamilyMismatchError(L"volume-*", L"bind")},
347 {L"type=volume,source=data-volume,target=/data,bind-propagation=rprivate",
348 Localization::WSLCCLI_MountOptionFamilyMismatchError(L"bind-*", L"volume")},
342 - {L"type=volume,source=data-volume,target=/data,bind-recursive=enabled",
343 - Localization::WSLCCLI_MountOptionFamilyMismatchError(L"bind-*", L"volume")},
349 {L"type=volume,source=data-volume,target=/data,tmpfs-size=1m",
350 Localization::WSLCCLI_MountOptionFamilyMismatchError(L"tmpfs-*", L"volume")},
351 {L"type=tmpfs,target=/tmp,volume-label=a=b", Localization::WSLCCLI_MountOptionFamilyMismatchError(L"volume-*", L"tmpfs")},
@@ -357,6 +362,7 @@ namespace {
362 {L"type=tmpfs,target=/tmp,tmpfs-size=9223372036854775808",
363 Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-size", L"9223372036854775808")},
364 {L"type=tmpfs,target=/tmp,tmpfs-mode=", Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-mode", L"")},
365 + {L"type=tmpfs,target=/tmp,tmpfs-mode=+0700", Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-mode", L"+0700")},
366 {L"type=tmpfs,target=/tmp,tmpfs-mode=-1", Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-mode", L"-1")},
367 {L"type=tmpfs,target=/tmp,tmpfs-mode=8", Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-mode", L"8")},
368 {L"type=tmpfs,target=/tmp,tmpfs-mode=0899", Localization::WSLCCLI_MountInvalidValueError(L"tmpfs-mode", L"0899")},
@@ -394,7 +400,7 @@ class WSLCCLIMountParserUnitTests
400 VERIFY_ARE_EQUAL(testCase.TmpfsMode.value(), actual.TmpfsMode.value());
401 }
402
397 - const auto actualTmpfsOptions = actual.MountType == mount::Type::Tmpfs ? mount::FormatTmpfsOptions(actual) : std::string{};
403 + const auto actualTmpfsOptions = actual.MountType == WSLCMountTypeTmpfs ? mount::FormatTmpfsOptions(actual) : std::string{};
404 VERIFY_ARE_EQUAL(std::string(testCase.TmpfsOptions), actualTmpfsOptions);
405 }
406 }
@@ -434,20 +440,36 @@ class WSLCCLIMountParserUnitTests
440 TEST_METHOD(Volume_ValidCases)
441 {
442 const auto bind = mount::ParseDockerVolumeString(LR"(C:\hostPath:/data:ro)");
437 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Bind), static_cast<int>(bind.MountType));
443 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeBind), static_cast<int>(bind.MountType));
444 VERIFY_ARE_EQUAL(std::wstring(LR"(C:\hostPath)"), bind.Source);
445 VERIFY_ARE_EQUAL(std::string("/data"), bind.Target);
446 VERIFY_IS_TRUE(bind.ReadOnly);
447 VERIFY_ARE_EQUAL(static_cast<int>(mount::BindSourcePolicy::CreateIfMissing), static_cast<int>(bind.BindSource));
448
449 + const auto relativeBind = mount::ParseDockerVolumeString(L".\\mount:/data");
450 + VERIFY_ARE_EQUAL(std::filesystem::weakly_canonical(std::filesystem::current_path() / L"mount").wstring(), relativeBind.Source);
451 +
452 const auto volume = mount::ParseDockerVolumeString(L"named-volume:/data");
444 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Volume), static_cast<int>(volume.MountType));
453 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeVolume), static_cast<int>(volume.MountType));
454 VERIFY_ARE_EQUAL(std::wstring(L"named-volume"), volume.Source);
455 VERIFY_ARE_EQUAL(std::string("/data"), volume.Target);
456 VERIFY_IS_FALSE(volume.ReadOnly);
457 VERIFY_ARE_EQUAL(static_cast<int>(mount::BindSourcePolicy::RequireExisting), static_cast<int>(volume.BindSource));
458 }
459
460 + TEST_METHOD(Volume_InvalidHostPath)
461 + {
462 + try
463 + {
464 + (void)mount::ParseDockerVolumeString(L"::/container:ro");
465 + VERIFY_FAIL(L"Expected MountParseException for an invalid host path");
466 + }
467 + catch (const mount::MountParseException& ex)
468 + {
469 + VERIFY_ARE_EQUAL(Localization::WSLCCLI_VolumeHostPathInvalid(L"::/container:ro", L":"), ex.Reason());
470 + }
471 + }
472 +
473 TEST_METHOD(Mount_DotRelativeBindSourceUsesCurrentDirectory)
474 {
475 const auto expected = (std::filesystem::current_path() / L"mount").lexically_normal().wstring();
@@ -458,28 +480,28 @@ class WSLCCLIMountParserUnitTests
480 TEST_METHOD(Mount_TypedSpecsAreValidated)
481 {
482 const mount::Spec relativeBind{
461 - .MountType = mount::Type::Bind,
483 + .MountType = WSLCMountTypeBind,
484 .Source = L"relative",
485 .Target = "/data",
486 };
487 VERIFY_THROWS(mount::ValidateMountSpec(relativeBind), mount::MountValidationException);
488
489 const mount::Spec relativeTarget{
468 - .MountType = mount::Type::Volume,
490 + .MountType = WSLCMountTypeVolume,
491 .Source = L"data-volume",
492 .Target = "data",
493 };
494 VERIFY_THROWS(mount::ValidateMountSpec(relativeTarget), mount::MountValidationException);
495
496 const mount::Spec tmpfsWithSource{
475 - .MountType = mount::Type::Tmpfs,
497 + .MountType = WSLCMountTypeTmpfs,
498 .Source = L"data-volume",
499 .Target = "/data",
500 };
501 VERIFY_THROWS(mount::ValidateMountSpec(tmpfsWithSource), mount::MountValidationException);
502
503 const mount::Spec bindWithTmpfsOptions{
482 - .MountType = mount::Type::Bind,
504 + .MountType = WSLCMountTypeBind,
505 .Source = L"C:\\data",
506 .Target = "/data",
507 .TmpfsSizeBytes = 1024,
@@ -487,14 +509,14 @@ class WSLCCLIMountParserUnitTests
509 VERIFY_THROWS(mount::ValidateMountSpec(bindWithTmpfsOptions), mount::MountValidationException);
510
511 const mount::Spec negativeTmpfsSize{
490 - .MountType = mount::Type::Tmpfs,
512 + .MountType = WSLCMountTypeTmpfs,
513 .Target = "/data",
514 .TmpfsSizeBytes = -1,
515 };
516 VERIFY_THROWS(mount::ValidateMountSpec(negativeTmpfsSize), mount::MountValidationException);
517
518 const mount::Spec tmpfs{
497 - .MountType = mount::Type::Tmpfs,
519 + .MountType = WSLCMountTypeTmpfs,
520 .Target = "/data",
521 .TmpfsSizeBytes = 1024,
522 .TmpfsMode = 0700,
@@ -502,8 +524,8 @@ class WSLCCLIMountParserUnitTests
524 VERIFY_NO_THROW(mount::ValidateMountSpec(tmpfs));
525
526 const mount::Spec duplicateMounts[] = {
505 - {.MountType = mount::Type::Tmpfs, .Target = "/data"},
506 - {.MountType = mount::Type::Volume, .Source = L"data-volume", .Target = "/data/"},
527 + {.MountType = WSLCMountTypeTmpfs, .Target = "/data"},
528 + {.MountType = WSLCMountTypeVolume, .Source = L"data-volume", .Target = "/data/"},
529 };
530 try
531 {
@@ -515,33 +537,12 @@ class WSLCCLIMountParserUnitTests
537 VERIFY_ARE_EQUAL(static_cast<int>(mount::ValidationError::DuplicateDestination), static_cast<int>(ex.Error()));
538 VERIFY_ARE_EQUAL(std::string("/data"), ex.Destination());
539 }
518 - }
540
520 - TEST_METHOD(Mount_DuplicateDestinationsAreRejected)
521 - {
522 - ContainerOptions options;
523 - options.Mounts = {
524 - mount::ParseDockerTmpfsString(L"/data"),
525 - {.MountType = mount::Type::Volume, .Source = L"data-volume", .Target = "/data/"},
526 - };
527 - VERIFY_THROWS(ValidateUniqueMountDestinations(options), wil::ResultException);
528 -
529 - options.Mounts = {
530 - {.MountType = mount::Type::Tmpfs, .Target = "/data/../cache"},
531 - {.MountType = mount::Type::Volume, .Source = L"data-volume", .Target = "/cache"},
532 - };
533 - VERIFY_THROWS(ValidateUniqueMountDestinations(options), wil::ResultException);
534 - }
535 -
536 - TEST_METHOD(Mount_UniqueDestinationsAreAccepted)
537 - {
538 - ContainerOptions options;
539 - options.Mounts = {
540 - mount::ParseDockerTmpfsString(L"/cache"),
541 - {.MountType = mount::Type::Volume, .Source = L"data-volume", .Target = "/data"},
542 - {.MountType = mount::Type::Bind, .Source = L"C:\\logs", .Target = "/logs"},
541 + const mount::Spec distinctBackslashMounts[] = {
542 + {.MountType = WSLCMountTypeTmpfs, .Target = "/data\\cache"},
543 + {.MountType = WSLCMountTypeVolume, .Source = L"data-volume", .Target = "/data/cache"},
544 };
544 - VERIFY_NO_THROW(ValidateUniqueMountDestinations(options));
545 + VERIFY_NO_THROW(mount::ValidateMountCollection(distinctBackslashMounts));
546 }
547 };
548
test/windows/wslc/WSLCCLITmpfsParserUnitTests.cpp
+1 -1
@@ -41,7 +41,7 @@ class WSLCCLITmpfsParserUnitTests
41 for (const auto& [input, expectedTarget, expectedOptions] : validTmpfsSpecs)
42 {
43 const auto result = mount::ParseDockerTmpfsString(input);
44 - VERIFY_ARE_EQUAL(static_cast<int>(mount::Type::Tmpfs), static_cast<int>(result.MountType));
44 + VERIFY_ARE_EQUAL(static_cast<int>(WSLCMountTypeTmpfs), static_cast<int>(result.MountType));
45 VERIFY_ARE_EQUAL(expectedTarget, result.Target);
46 VERIFY_IS_TRUE(result.TmpfsOptions.has_value());
47 VERIFY_ARE_EQUAL(expectedOptions, result.TmpfsOptions.value());