Skip to content

Update volume APIs to match docker arguments#40181

Open
kvega005 wants to merge 4 commits intomicrosoft:feature/wsl-for-appsfrom
kvega005:volumeManagement
Open

Update volume APIs to match docker arguments#40181
kvega005 wants to merge 4 commits intomicrosoft:feature/wsl-for-appsfrom
kvega005:volumeManagement

Conversation

@kvega005
Copy link
Copy Markdown

Summary of the Pull Request

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

Copilot AI review requested due to automatic review settings April 14, 2026 22:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@kvega005 kvega005 requested a review from Copilot April 15, 2026 01:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.

Comment on lines +1465 to +1472
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);
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1963 to +1971
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);
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1988 to +2000
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;

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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"));

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +36
enum class DetachVolumeFlags
{
None = 0,
DeleteVhd = 1,
};
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()).

Copilot uses AI. Check for mistakes.
m_virtualMachine.DetachDisk(m_lun);
m_attached = false;

if (flags == DetachVolumeFlags::DeleteVhd)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (flags == DetachVolumeFlags::DeleteVhd)
if ((static_cast<unsigned int>(flags) & static_cast<unsigned int>(DetachVolumeFlags::DeleteVhd)) != 0)

Copilot uses AI. Check for mistakes.
</data>
<data name = "MessageWslcInvalidVolumeOptions" xml:space = "preserve" >
<value>Invalid volume options: '{}'</value>
<value>Missing required option: {}</value>
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<value>Missing required option: {}</value>
<value>Invalid volume options: {}</value>

Copilot uses AI. Check for mistakes.
}
CATCH_RETURN();

HRESULT WSLCSession::PruneVolumes(const WSLCPruneVolumesOptions* Options, WSLCPruneVolumesResults* Results)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@kvega005 kvega005 marked this pull request as ready for review April 15, 2026 01:46
@kvega005 kvega005 requested a review from a team as a code owner April 15, 2026 01:46
Copy link
Copy Markdown
Collaborator

@OneBlue OneBlue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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] == '-');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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{};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Given that we don't need cryptographically secure random bits, I'd recommend using std::generate here (example:

const std::independent_bits_engine<std::default_random_engine, 16, unsigned short> random;
// independent_bits_engine doesn't support unsigned char on MSVC
auto* macAddressShort = reinterpret_cast<unsigned short*>(MacAddress.data());
std::generate(macAddressShort, macAddressShort + 3, random);
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants