Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ba81b75
Add hourly upstream sync workflow for feature/wsl-for-apps
kvega005 Mar 5, 2026
50f9ce0
Remove upstream sync workflow
kvega005 Mar 5, 2026
c55426c
Merge branch 'microsoft:master' into master
kvega005 Mar 23, 2026
0055811
Merge branch 'microsoft:master' into master
kvega005 Apr 1, 2026
de88707
Merge branch 'microsoft:master' into master
kvega005 Apr 12, 2026
f16fc61
Merge branch 'microsoft:master' into master
kvega005 Apr 30, 2026
da31dd2
Merge branch 'microsoft:master' into master
kvega005 May 1, 2026
b95f237
Merge branch 'microsoft:master' into master
kvega005 May 1, 2026
2ae93aa
Merge branch 'microsoft:master' into master
kvega005 May 6, 2026
f805f05
Merge branch 'microsoft:master' into master
kvega005 May 7, 2026
f2b4090
Merge branch 'microsoft:master' into master
kvega005 May 13, 2026
07445a2
Merge branch 'microsoft:master' into master
kvega005 May 15, 2026
9a29435
Merge branch 'microsoft:master' into master
kvega005 May 18, 2026
ba3bc80
Merge branch 'microsoft:master' into master
kvega005 May 19, 2026
d11cbbc
Merge branch 'microsoft:master' into master
kvega005 May 21, 2026
eaae1bc
Merge branch 'microsoft:master' into master
kvega005 May 22, 2026
9f6a16c
Merge branch 'microsoft:master' into master
kvega005 May 27, 2026
dcaaccb
Merge branch 'master' of https://github.com/kvega005/WSL
Jun 11, 2026
119c17a
Merge branch 'microsoft:master' into master
kvega005 Jun 12, 2026
434d93c
Merge branch 'microsoft:master' into master
kvega005 Jun 18, 2026
eb224c9
Delete lost and found
kvega005 Jun 19, 2026
94470dd
Move test to wslc tests
kvega005 Jun 19, 2026
5f6781e
Fix comment
kvega005 Jun 19, 2026
083203f
Address feedback
kvega005 Jun 22, 2026
ee6d710
Fix Localization
kvega005 Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3316,6 +3316,10 @@ On first run, creates the file with all settings commented out at their defaults
<value>Failed to unmount volume '{}': {}</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcVolumeLostFoundNotEmpty" xml:space="preserve">
<value>Volume '{}' has a non-empty 'lost+found' directory. It was left in place and the volume may not be seeded with image contents.</value>
<comment>{Locked="lost+found"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcContainerStopAfterPluginRejectionFailed" xml:space="preserve">
<value>Failed to stop container '{}' after plugin rejection</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down
39 changes: 38 additions & 1 deletion src/linux/init/WSLCInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,43 @@ void HandleMessageImpl(
Transaction.Send<WSLC_GET_DISK::TResponse>(writer.Span());
}

void HandleMessageImpl(
wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_LISTDIR& Message, const gsl::span<gsl::byte>& Buffer)
{
wsl::shared::MessageWriter<WSLC_LISTDIR_RESULT> writer;

try
{
const auto* path = wsl::shared::string::FromMessageBuffer<WSLC_LISTDIR>(Buffer);
THROW_ERRNO_IF(EINVAL, path == nullptr);

wil::unique_dir dir{opendir(path)};
THROW_LAST_ERROR_IF(!dir);

std::vector<std::string> entries;
for (dirent64* entry = readdir64(dir.get()); entry != nullptr; entry = readdir64(dir.get()))
{
const std::string_view name{entry->d_name};
if (name == "." || name == "..")
{
continue;
}

entries.emplace_back(name);
}

auto pointers = wsl::shared::string::StringPointersFromArray(entries, false);
writer.WriteStringArray(writer->EntriesIndex, pointers.data(), pointers.size());
writer->Result = 0;
}
catch (...)
{
writer->Result = wil::ResultFromCaughtException();
}

Transaction.Send<WSLC_LISTDIR::TResponse>(writer.Span());
}

void HandleMessageImpl(
wsl::shared::SocketChannel& Channel,
wsl::shared::Transaction& Transaction,
Expand Down Expand Up @@ -952,7 +989,7 @@ void ProcessMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transactio
{
try
{
HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES>(
HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR>(
Channel, Transaction, Type, Buffer);
}
catch (...)
Expand Down
31 changes: 31 additions & 0 deletions src/shared/inc/lxinitshared.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ typedef enum _LX_MESSAGE_TYPE
LxMessageWSLCUnixConnect,
LxMessageWSLCGetGuestCapabilities,
LxMessageWSLCGetGuestCapabilitiesResult,
LxMessageWSLCListDir,
LxMessageWSLCListDirResult,
} LX_MESSAGE_TYPE,
*PLX_MESSAGE_TYPE;

Expand Down Expand Up @@ -522,6 +524,8 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
X(LxMessageWSLCUnixConnect)
X(LxMessageWSLCGetGuestCapabilities)
X(LxMessageWSLCGetGuestCapabilitiesResult)
X(LxMessageWSLCListDir)
X(LxMessageWSLCListDirResult)

default:
return "<unexpected LX_MESSAGE_TYPE>";
Expand Down Expand Up @@ -1585,6 +1589,33 @@ struct WSLC_GET_DISK
PRETTY_PRINT(FIELD(Header), FIELD(ScsiLun));
};

struct WSLC_LISTDIR_RESULT
{
static inline auto Type = LxMessageWSLCListDirResult;

DECLARE_MESSAGE_CTOR(WSLC_LISTDIR_RESULT);

MESSAGE_HEADER Header;
int Result{};
unsigned int EntriesIndex{};
char Buffer[];

PRETTY_PRINT(FIELD(Header), FIELD(Result), STRING_ARRAY_FIELD(EntriesIndex));
};

struct WSLC_LISTDIR
{
static inline auto Type = LxMessageWSLCListDir;
using TResponse = WSLC_LISTDIR_RESULT;

DECLARE_MESSAGE_CTOR(WSLC_LISTDIR);

MESSAGE_HEADER Header;
char Buffer[];

PRETTY_PRINT(FIELD(Header), FIELD(Buffer));
};

struct WSLC_MOUNT_RESULT
{
static inline auto Type = LxMessageWSLCMountResult;
Expand Down
34 changes: 34 additions & 0 deletions src/windows/wslcsession/WSLCVhdVolume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ namespace {
return name;
}

void RemoveLostFoundDirectory(WSLCVirtualMachine& VirtualMachine, const std::string& VolumeName, const std::string& MountPath)
try
{
constexpr auto c_lostFoundDir = "lost+found";
const auto entries = VirtualMachine.ListDirectory(MountPath);

// Only remove lost+found if the disk is empty besides that directory.
if (entries.size() != 1 || entries.front() != c_lostFoundDir)
{
return;
}

try
{
VirtualMachine.RemoveDirectory(std::format("{}/{}", MountPath, c_lostFoundDir));
}
catch (...)
{
// rmdir only removes an empty directory, so reaching here means the
// lone lost+found captured recovered data. Leave it and warn.
LOG_CAUGHT_EXCEPTION();
EMIT_USER_WARNING(Localization::MessageWslcVolumeLostFoundNotEmpty(VolumeName));
}
Comment thread
kvega005 marked this conversation as resolved.
}
CATCH_LOG();
} // namespace

WSLCVhdVolumeImpl::WSLCVhdVolumeImpl(
Expand Down Expand Up @@ -151,6 +176,13 @@ std::unique_ptr<WSLCVhdVolumeImpl> WSLCVhdVolumeImpl::Create(

auto mountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { VirtualMachine.Unmount(virtualMachinePath.c_str()); });

// mkfs.ext4 always creates a lost+found directory at the filesystem root,
Comment thread
kvega005 marked this conversation as resolved.
// which makes a freshly formatted volume look non-empty to Docker and
// suppresses the copy-up that seeds image data on first use. Drop it so
// Docker seeds the volume with the image's contents. No-op when the volume
// already contains data.
RemoveLostFoundDirectory(VirtualMachine, name, virtualMachinePath);

WSLCVolumeMetadata metadata;
metadata.Driver = WSLCVhdVolumeDriver;
metadata.DriverOpts = DriverOpts;
Expand Down Expand Up @@ -246,6 +278,8 @@ std::unique_ptr<WSLCVhdVolumeImpl> WSLCVhdVolumeImpl::Open(
VirtualMachine.Mount(device.c_str(), virtualMachinePath.c_str(), "ext4", "", 0);
auto mountCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { VirtualMachine.Unmount(virtualMachinePath.c_str()); });

RemoveLostFoundDirectory(VirtualMachine, Volume.Name, virtualMachinePath);

lun = attachedLun;
attached = true;

Expand Down
27 changes: 27 additions & 0 deletions src/windows/wslcsession/WSLCVirtualMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,33 @@ void WSLCVirtualMachine::Ext4Format(const std::string& Device, std::optional<uin
THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str());
}

void WSLCVirtualMachine::RemoveDirectory(const std::string& Path)
{
// rmdir only removes an empty directory, so callers can rely on it to leave
// a non-empty directory untouched.
constexpr auto rmdirPath = "/bin/rmdir";

std::vector<std::string> args = {rmdirPath, Path};

ServiceProcessLauncher launcher(rmdirPath, args);
auto result = launcher.Launch(*this).WaitAndCaptureOutput();

THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str());
}

std::vector<std::string> WSLCVirtualMachine::ListDirectory(const std::string& Path)
{
wsl::shared::MessageWriter<WSLC_LISTDIR> message;
message.WriteString(Path);

gsl::span<gsl::byte> responseSpan;
const auto& response = m_initChannel.Transaction<WSLC_LISTDIR>(message.Span(), &responseSpan, m_initChannelTimeout);

THROW_HR_IF_MSG(E_FAIL, response.Result != 0, "Failed to list directory '%hs', init returned: %d", Path.c_str(), response.Result);

return wsl::shared::string::ArrayFromSpan(responseSpan, response.EntriesIndex);
}

void WSLCVirtualMachine::Unmount(_In_ const char* Path)
{
auto [pid, _, subChannel] = Fork(WSLC_FORK::Thread);
Expand Down
2 changes: 2 additions & 0 deletions src/windows/wslcsession/WSLCVirtualMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ class WSLCVirtualMachine
void DetachDisk(_In_ ULONG Lun);
void Ext4Format(_In_ const std::string& Device, _In_ std::optional<uint32_t> Uid = std::nullopt, _In_ std::optional<uint32_t> Gid = std::nullopt);
void Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
void RemoveDirectory(_In_ const std::string& Path);
std::vector<std::string> ListDirectory(_In_ const std::string& Path);

wil::unique_socket ConnectUnixSocket(_In_ const char* Path);
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(enum WSLC_FORK::ForkType Type);
Expand Down
41 changes: 41 additions & 0 deletions test/windows/WSLCTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4003,6 +4003,47 @@ class WSLCTests
VERIFY_IS_FALSE(std::filesystem::exists(volumeVhdPath));
}

WSLC_TEST_METHOD(NamedVolumesVhdSeedsImageData)
{
// A freshly formatted VHD volume must be seeded with the image's content
// on first use, just like a guest volume. mkfs.ext4 creates a lost+found
// directory at the volume root; if it isn't removed, Docker treats the
// volume as non-empty and skips the copy-up that seeds image data.
// Mounting the empty volume over a directory the image is guaranteed to
// populate (/etc) exercises that copy-up.
WSLCDriverOption driverOpts[] = {{"SizeBytes", "1073741824"}};
const std::string volumeName = "wslc-test-named-volume-vhd-seed";

LOG_IF_FAILED(m_defaultSession->DeleteVolume(volumeName.c_str()));

WSLCVolumeOptions volumeOptions{};
volumeOptions.Name = volumeName.c_str();
volumeOptions.Driver = "vhd";
volumeOptions.DriverOpts = driverOpts;
volumeOptions.DriverOptsCount = ARRAYSIZE(driverOpts);

WSLCVolumeInformation volInfo{};
VERIFY_SUCCEEDED(m_defaultSession->CreateVolume(&volumeOptions, &volInfo));
auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteVolume(volumeName.c_str())); });

WSLCContainerLauncher launcher("debian:latest", "wslc-vhd-seed-container", {"/bin/sh", "-c", "ls -A /etc"});
launcher.AddNamedVolume(volumeName, "/etc", false);

auto container = launcher.Launch(*m_defaultSession);
auto result = container.GetInitProcess().WaitAndCaptureOutput();

VERIFY_ARE_EQUAL(0, result.Code);

// Image content was seeded into the volume...
VERIFY_IS_TRUE(
result.Output[1].find("passwd") != std::string::npos,
L"Image's /etc content should be seeded into the fresh VHD volume");

// ...and the ext4 lost+found is gone, so it never blocked copy-up.
VERIFY_IS_TRUE(
result.Output[1].find("lost+found") == std::string::npos, L"lost+found should have been removed from the volume root");
}

WSLC_TEST_METHOD(NamedVolumesGuest)
{
ValidateNamedVolumeContract("guest", nullptr, 0);
Expand Down