Update volume APIs to match docker arguments#40181
Update volume APIs to match docker arguments#40181kvega005 wants to merge 4 commits intomicrosoft:feature/wsl-for-appsfrom
Conversation
| std::map<std::string, std::string> wsl::windows::common::wslutil::ParseKeyValuePairs(const KeyValuePair* pairs, ULONG count, LPCSTR reservedKey) | ||
| { | ||
| std::map<std::string, std::string> result; | ||
|
|
||
| for (ULONG i = 0; i < count; i++) | ||
| { | ||
| THROW_HR_IF_NULL_MSG(E_INVALIDARG, pairs[i].Key, "Key at index %lu is null", i); | ||
| THROW_HR_IF_NULL_MSG(E_INVALIDARG, pairs[i].Value, "Value at index %lu is null", i); |
There was a problem hiding this comment.
ParseKeyValuePairs dereferences pairs[i] without validating pairs itself. If a caller passes count > 0 with a null pointer (e.g., via COM misuse or a future call site bug), this will AV. Add an explicit guard at the start like “if (count > 0) THROW_HR_IF_NULL(E_INVALIDARG, pairs);” (or return empty only when count == 0). This also protects WSLCSession::CreateVolume which immediately calls this helper on Options->DriverOpts/Labels.
| auto lock = m_lock.lock_shared(); | ||
| std::lock_guard volumesLock(m_volumesLock); | ||
|
|
||
| THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient); | ||
| docker_schema::PruneVolumeResult pruneResult; | ||
| try | ||
| { | ||
| pruneResult = m_dockerClient->PruneVolumes(filters); | ||
| } |
There was a problem hiding this comment.
PruneVolumes holds m_volumesLock while making the Docker HTTP call, which can block all other volume operations for the duration of the network request. Consider calling m_dockerClient->PruneVolumes(filters) before acquiring m_volumesLock, then take m_volumesLock only to reconcile m_volumes with VolumesDeleted. This reduces contention and lowers the chance of lock-induced latency spikes.
| WSL_LOG("VolumesPruned", TraceLoggingValue(Results->VolumesCount, "PrunedCount")); | ||
|
|
||
| auto volumeCount = pruneResult.VolumesDeleted.has_value() ? pruneResult.VolumesDeleted->size() : 0; | ||
| auto output = wil::make_unique_cotaskmem<WSLCVolumeName[]>(volumeCount); | ||
| for (size_t i = 0; i < volumeCount; ++i) | ||
| { | ||
| strcpy_s(output[i], pruneResult.VolumesDeleted.value()[i].c_str()); | ||
| } | ||
|
|
||
| Results->Volumes = output.release(); | ||
| Results->VolumesCount = static_cast<ULONG>(volumeCount); | ||
| Results->SpaceReclaimed = pruneResult.SpaceReclaimed; | ||
|
|
There was a problem hiding this comment.
Two concrete issues here: (1) the log uses Results->VolumesCount before it’s populated (it will always log 0 due to the earlier ZeroMemory). Log volumeCount (or log after setting Results->VolumesCount). (2) strcpy_s’s return value is ignored; if a Docker-reported volume name exceeds WSLC_MAX_VOLUME_NAME_LENGTH, this will fail silently and you’ll return an empty string element. Check the return and fail the call (or skip/clip with an explicit policy) if copying doesn’t succeed.
| WSL_LOG("VolumesPruned", TraceLoggingValue(Results->VolumesCount, "PrunedCount")); | |
| auto volumeCount = pruneResult.VolumesDeleted.has_value() ? pruneResult.VolumesDeleted->size() : 0; | |
| auto output = wil::make_unique_cotaskmem<WSLCVolumeName[]>(volumeCount); | |
| for (size_t i = 0; i < volumeCount; ++i) | |
| { | |
| strcpy_s(output[i], pruneResult.VolumesDeleted.value()[i].c_str()); | |
| } | |
| Results->Volumes = output.release(); | |
| Results->VolumesCount = static_cast<ULONG>(volumeCount); | |
| Results->SpaceReclaimed = pruneResult.SpaceReclaimed; | |
| auto volumeCount = pruneResult.VolumesDeleted.has_value() ? pruneResult.VolumesDeleted->size() : 0; | |
| auto output = wil::make_unique_cotaskmem<WSLCVolumeName[]>(volumeCount); | |
| for (size_t i = 0; i < volumeCount; ++i) | |
| { | |
| const errno_t copyResult = strcpy_s(output[i], pruneResult.VolumesDeleted.value()[i].c_str()); | |
| RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), copyResult != 0); | |
| } | |
| Results->Volumes = output.release(); | |
| Results->VolumesCount = static_cast<ULONG>(volumeCount); | |
| Results->SpaceReclaimed = pruneResult.SpaceReclaimed; | |
| WSL_LOG("VolumesPruned", TraceLoggingValue(Results->VolumesCount, "PrunedCount")); |
| enum class DetachVolumeFlags | ||
| { | ||
| None = 0, | ||
| DeleteVhd = 1, | ||
| }; |
There was a problem hiding this comment.
DetachVolumeFlags is modeled like a flags enum, but it’s currently used as a scalar (and there are no flag operators). Either (a) make it a simple enum (non-flags) and name it accordingly, or (b) define flag operators (e.g., DEFINE_ENUM_FLAG_OPERATORS) and treat it as a bitmask consistently (see also the equality check in Detach()).
| m_virtualMachine.DetachDisk(m_lun); | ||
| m_attached = false; | ||
|
|
||
| if (flags == DetachVolumeFlags::DeleteVhd) |
There was a problem hiding this comment.
If DetachVolumeFlags is intended to be a bitmask, checking with == will behave incorrectly once additional flags are added (e.g., DeleteVhd | Force). Use a bit-test instead (after adding flag operators), or keep the type as a non-flags enum if only one mode is intended.
| if (flags == DetachVolumeFlags::DeleteVhd) | |
| if ((static_cast<unsigned int>(flags) & static_cast<unsigned int>(DetachVolumeFlags::DeleteVhd)) != 0) |
| </data> | ||
| <data name = "MessageWslcInvalidVolumeOptions" xml:space = "preserve" > | ||
| <value>Invalid volume options: '{}'</value> | ||
| <value>Missing required option: {}</value> |
There was a problem hiding this comment.
The resource key name MessageWslcInvalidVolumeOptions no longer matches the updated text (“Missing required option…”). This makes the API harder to understand/maintain and increases the chance of incorrect reuse elsewhere. Consider introducing a new localized resource with a matching name (and updating call sites), or revert this one to a generic “invalid options” message and add a dedicated “missing option” message.
| <value>Missing required option: {}</value> | |
| <value>Invalid volume options: {}</value> |
| } | ||
| CATCH_RETURN(); | ||
|
|
||
| HRESULT WSLCSession::PruneVolumes(const WSLCPruneVolumesOptions* Options, WSLCPruneVolumesResults* Results) |
There was a problem hiding this comment.
PruneVolumes introduces new behavior (filter translation, calling Docker prune, and reconciling m_volumes + deleting VHDs) but there are no corresponding updates in test/windows/WSLCTests.cpp in this PR. Add at least one test covering: (1) pruning removes WSLC-managed volumes from ListVolumes, and (2) Detach(DeleteVhd) path deletes the underlying VHD file when Docker reports the volume as deleted.
OneBlue
left a comment
There was a problem hiding this comment.
Change looks great! Couple minor comments
| THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcInvalidVolumeOptions("SizeBytes"), it == DriverOpts.end()); | ||
|
|
||
| auto& value = it->second; | ||
| THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageInvalidSize(value), value.empty() || value[0] == '-'); |
There was a problem hiding this comment.
Is this check required ? I'd expect ToUInt64() to fail if value was empty or negative
| WSLCDriverOption driverOpts[] = {{"SizeBytes", "1073741824"}}; | ||
| volumeOptions.DriverOpts = driverOpts; | ||
| volumeOptions.DriverOptsCount = ARRAYSIZE(driverOpts); | ||
| WSLCVolumeInformation volInfo{}; |
There was a problem hiding this comment.
nit: Here we're leaking the pointers returned by CreateVolume().
We might want to create a helper similar to [this][https://github.com/microsoft/WSL/blob/f60ec519d5add21a450d9f9d14a0239f7a99de5d/src/windows/common/wslutil.h#L130) to make it easier for callers to properly deallocate the volume info
| [unique] LPCSTR Options; | ||
| [unique] LPCSTR Name; | ||
| [unique] LPCSTR Driver; | ||
| [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; |
There was a problem hiding this comment.
Given that DriverOpts and Labels require memory allocation & deallocation, I would recommend removing them from this structure to make things easier for the caller (especially since they're not needed for the simple wslc volume ls case).
If the caller needs to see the driver options or labels, they can call Inspect()
| WSLCVolumeMetadata metadata{}; | ||
| metadata.Type = WSLCVhdVolumeType; | ||
| BYTE randomBytes[32]; | ||
| THROW_IF_NTSTATUS_FAILED(BCryptGenRandom(nullptr, randomBytes, sizeof(randomBytes), BCRYPT_USE_SYSTEM_PREFERRED_RNG)); |
There was a problem hiding this comment.
nit: Given that we don't need cryptographically secure random bits, I'd recommend using std::generate here (example:
WSL/src/windows/common/WslCoreConfig.cpp
Lines 166 to 171 in acd3cd4
Summary of the Pull Request
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed