Move from shipping the initrd to generating it during install. (#14119)
* Move from shipping the initrd to generating during package install. * pr feedback * working * adjust custom action conditions * update initrd test to cover more cases * Update msipackage/package.wix.in Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * use stack buffer * move initrd helper to filesystem.cpp and add unit test --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Ben Hillis committed
Feb 7, 2026 at 12:47 UTC
0e263df6f36c71ee8cdb84406620458047316ba8
16 files changed
+313
-310
.pipelines/build-stage.yml
+1
-1
@@ -23,7 +23,7 @@ parameters:
23
- name: targets
24
type: object
25
default:
26
- - target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslinstaller;wslinstall;initramfs;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
26
+ - target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslinstaller;wslinstall;init;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
27
pattern: "wsl.exe,libwsl.dll,wslg.exe,wslservice.exe,wslhost.exe,wslrelay.exe,wslinstaller.exe,wslinstall.dll,wslserviceproxystub.dll,wslsettings/wslsettings.dll,wslsettings/wslsettings.exe,wslinstallerproxystub.dll,wsldevicehost.dll,WSLDVCPlugin.dll,testplugin.dll,wsldeps.dll"
28
- target: "msixgluepackage"
29
pattern: "gluepackage.msix"
CMakeLists.txt
-2
@@ -66,7 +66,6 @@ find_nuget_package(Microsoft.WSL.bsdtar BSDTARD /build/native/bin)
66
find_nuget_package(Microsoft.WSL.LinuxSdk LINUXSDK /)
67
find_nuget_package(Microsoft.WSL.TestDistro TEST_DISTRO /)
68
find_nuget_package(Microsoft.WSLg WSLG /build/native/bin)
69
-find_nuget_package(StrawberryPerl PERL /)
69
find_nuget_package(vswhere VSWHERE /tools)
70
find_nuget_package(Wix WIX /tools/net6.0/any)
71
@@ -92,7 +91,6 @@ find_nuget_package(Microsoft.Windows.SDK.NET.Ref WINDOWS_SDK_DOTNET /)
91
find_nuget_package(Microsoft.Xaml.Behaviors.WinUI.Managed XAML_BEHAVIORS /)
92
find_nuget_package(WinUIEx WINUIEX /)
93
95
-set(BIN2CPIO ${CMAKE_CURRENT_LIST_DIR}/tools/bin2cpio/bin2cpio.pl)
94
set(WSLG_TS_PLUGIN_DLL "WSLDVCPlugin.dll")
95
96
# Default to debug build if unspecified
msipackage/CMakeLists.txt
+2
-2
@@ -22,7 +22,7 @@ foreach(binary ${WINDOWS_BINARIES})
22
list(APPEND BINARIES_DEPENDENCIES "${PACKAGE_INPUT_DIR}/${binary}")
23
endforeach()
24
25
-set(LINUX_BINARIES init;initrd.img)
25
+set(LINUX_BINARIES init)
26
foreach(binary ${LINUX_BINARIES})
27
list(APPEND BINARIES_DEPENDENCIES "${BIN}/${binary}")
28
endforeach()
@@ -52,7 +52,7 @@ add_custom_command(
52
53
add_custom_target(msipackage DEPENDS ${OUTPUT_PACKAGE})
54
set_target_properties(msipackage PROPERTIES EXCLUDE_FROM_ALL FALSE SOURCES ${PACKAGE_WIX_IN})
55
-add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslserviceproxystub init initramfs wslinstall msixgluepackage)
55
+add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslserviceproxystub init wslinstall msixgluepackage)
56
57
if (WSL_BUILD_WSL_SETTINGS)
58
add_dependencies(msipackage wslsettings libwsl)
msipackage/package.wix.in
+24
-2
@@ -261,7 +261,6 @@
261
<DirectoryRef Id="TOOLSFOLDER">
262
<Component Id="tools" Guid="F0C8D6BA-1502-41E7-BF72-D93DFA134733" Bitness="always64" UninstallWhenSuperseded="yes">
263
<File Id="init" Source="${BIN}/init" Checksum="yes" />
264
- <File Id="initrd.img" Source="${BIN}/initrd.img"/>
264
<File Id="bsdtar" Source="${BSDTARD_SOURCE_DIR}/${TARGET_PLATFORM}/bsdtar"/>
265
266
<?if "${WSL_DEV_BINARY_PATH}" = "" ?>
@@ -461,7 +460,23 @@
460
Return="check"
461
Execute="deferred"
462
/>
464
-
463
+
464
+ <CustomAction Id="CreateInitrd"
465
+ Impersonate="no"
466
+ BinaryRef="wslinstall.dll"
467
+ DllEntry="CreateInitrd"
468
+ Return="check"
469
+ Execute="deferred"
470
+ />
471
+
472
+ <CustomAction Id="RemoveInitrd"
473
+ Impersonate="no"
474
+ BinaryRef="wslinstall.dll"
475
+ DllEntry="RemoveInitrd"
476
+ Return="check"
477
+ Execute="deferred"
478
+ />
479
+
480
<CustomAction Id="RemoveRegistryKeyProtections"
481
Impersonate="no"
482
BinaryRef="wslinstall.dll"
@@ -525,10 +540,17 @@
540
<!-- This needs to run after the registry is written because this action needs to read the install path -->
541
<Custom Action="RegisterLspCategories" After="WriteRegistryValues" Condition='((not REMOVE~="ALL") or WIX_UPGRADE_DETECTED) and (not UPGRADINGPRODUCTCODE) and (not SKIPLSP = 1)' />
542
543
+ <!-- Generate initrd.img from the init binary during installation.
544
+ Only run when the tools component is being installed ($tools=3), not during same-version reinstalls -->
545
+ <Custom Action="CreateInitrd" After="RegisterLspCategories" Condition='$tools=3' />
546
+
547
<!-- This needs to run before the registry is cleared because this action needs to read the install path.
548
This actions runs on uninstallation and upgrade. -->
549
<Custom Action="UnregisterLspCategories" Before="RemoveRegistryValues" Condition='(REMOVE~="ALL") and (not UPGRADINGPRODUCTCODE) and (not SKIPLSP = 1)' />
550
551
+ <!-- Remove initrd.img during uninstallation. Must run after StopServices so the file isn't locked -->
552
+ <Custom Action="RemoveInitrd" Before="UnregisterLspCategories" Condition='(REMOVE~="ALL") and (not UPGRADINGPRODUCTCODE)' />
553
+
554
<?if "${WSL_BUILD_WSL_SETTINGS}" = "true" ?>
555
<!-- This needs to run before the registry is written because this action calculates registry key names and values -->
556
<Custom Action="CalculateWslSettingsProtocolIds" Before="WriteRegistryValues" Condition='(not REMOVE~="ALL")' />
packages.config
-1
@@ -24,7 +24,6 @@
24
<package id="Microsoft.WSL.TestDistro" version="2.5.7-47" />
25
<package id="Microsoft.WSLg" version="1.0.73" />
26
<package id="Microsoft.Xaml.Behaviors.WinUI.Managed" version="3.0.0" />
27
- <package id="StrawberryPerl" version="5.32.1.1" />
27
<package id="vswhere" version="3.1.7" />
28
<package id="WinUIEx" version="2.9.0" />
29
<package id="Wix" version="5.0.2" />
src/linux/init/CMakeLists.txt
-17
@@ -50,21 +50,4 @@ set(INIT_LIBRARIES ${COMMON_LINUX_LINK_LIBRARIES} netlinkutil plan9 mountutil co
50
add_linux_executable(init "${SOURCES}" "${HEADERS}" "${INIT_LIBRARIES}")
51
add_dependencies(init localization)
52
53
-set(INITRAMFS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/initrd.img)
54
-set(INIT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/init)
55
-
56
-# The initramfs depends on both the init target and binary so it doesn't start too early
57
-# (before the symbols are stripped from the init binary)
58
-# but so that it is still rebuilt if the file itself changes.
59
-add_custom_command(
60
- OUTPUT ${INITRAMFS} "${CMAKE_CURRENT_BINARY_DIR}/CmakeFiles/initramfs"
61
- DEPENDS init ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/init
62
- COMMAND ${PERL_SOURCE_DIR}/bin/perl.exe ${BIN2CPIO} ${INIT} ${INITRAMFS}
63
- COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/CmakeFiles/initramfs"
64
- VERBATIM
65
- )
66
-
67
-add_custom_target(initramfs DEPENDS ${INITRAMFS})
68
-
53
set_target_properties(init PROPERTIES FOLDER linux)
70
-set_target_properties(initramfs PROPERTIES FOLDER linux)
src/linux/init/main.cpp
+6
@@ -3985,6 +3985,12 @@ int main(int Argc, char* Argv[])
3985
g_LogFd = 3;
3986
}
3987
3988
+ //
3989
+ // Log the WSL version to kmesg.
3990
+ //
3991
+
3992
+ LOG_INFO("WSL version {}", WSL_PACKAGE_VERSION);
3993
+
3994
//
3995
// Ensure /dev/console is present and set as the controlling terminal.
3996
// If opening /dev/console times out, stdout and stderr to the logging file descriptor.
src/windows/common/filesystem.cpp
+110
@@ -1115,6 +1115,116 @@ void wsl::windows::common::filesystem::UpdateInit(_In_ PCWSTR BasePath, _In_ ULO
1115
CopyFileWithMetadata(source.c_str(), dest.c_str(), (LX_S_IFREG | 0755), DistroVersion);
1116
}
1117
1118
+void wsl::windows::common::filesystem::CreateCpioInitrd(_In_ const std::filesystem::path& SourcePath, _In_ const std::filesystem::path& DestPath)
1119
+{
1120
+ // Open the source init binary
1121
+ wil::unique_hfile sourceFile{
1122
+ CreateFileW(SourcePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
1123
+ THROW_LAST_ERROR_IF(!sourceFile);
1124
+
1125
+ // Get file size
1126
+ LARGE_INTEGER fileSize{};
1127
+ THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(sourceFile.get(), &fileSize));
1128
+ THROW_HR_IF(E_INVALIDARG, fileSize.HighPart != 0); // File too large
1129
+
1130
+ const DWORD initSize = fileSize.LowPart;
1131
+ const auto initDataPadding = (4 - (initSize % 4)) % 4;
1132
+ wil::unique_hfile destFile{CreateFileW(DestPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
1133
+ THROW_LAST_ERROR_IF(!destFile);
1134
+
1135
+ // Clean up the destination file on failure.
1136
+ auto deleteFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1137
+ destFile.reset();
1138
+ LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(DestPath.c_str()));
1139
+ });
1140
+
1141
+ auto writeCpioHeader = [&destFile](DWORD fileSize, PCSTR name) {
1142
+ // CPIO newc header: magic(6) + 13 fields of 8 hex chars each = 110 bytes
1143
+ constexpr size_t headerSize = 110;
1144
+ const auto nameLen = strlen(name) + 1;
1145
+ const auto headerPadding = (4 - ((headerSize + nameLen) % 4)) % 4;
1146
+
1147
+ // Get current time for mtime. CPIO newc format only supports 32-bit fields.
1148
+ const auto mtime = static_cast<DWORD>(time(nullptr));
1149
+
1150
+ char header[headerSize + 1];
1151
+ sprintf_s(
1152
+ header,
1153
+ "070701" // magic
1154
+ "%08X" // inode
1155
+ "%08X" // mode
1156
+ "%08X" // uid
1157
+ "%08X" // gid
1158
+ "%08X" // nlink
1159
+ "%08X" // mtime
1160
+ "%08X" // filesize
1161
+ "%08X" // devmajor
1162
+ "%08X" // devminor
1163
+ "%08X" // rdevmajor
1164
+ "%08X" // rdevminor
1165
+ "%08X" // namesize
1166
+ "%08X", // check
1167
+ 0,
1168
+ (fileSize > 0) ? 0100755 : 0,
1169
+ 0,
1170
+ 0,
1171
+ (fileSize > 0) ? 1 : 0,
1172
+ mtime,
1173
+ fileSize,
1174
+ 0,
1175
+ 0,
1176
+ 0,
1177
+ 0,
1178
+ static_cast<DWORD>(nameLen),
1179
+ 0);
1180
+
1181
+ DWORD bytesWritten;
1182
+ THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), header, headerSize, &bytesWritten, nullptr));
1183
+
1184
+ // Write name with null terminator
1185
+ THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), name, static_cast<DWORD>(nameLen), &bytesWritten, nullptr));
1186
+
1187
+ // Write padding to align to 4 bytes
1188
+ if (headerPadding > 0)
1189
+ {
1190
+ char padding[4] = {0};
1191
+ THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), padding, static_cast<DWORD>(headerPadding), &bytesWritten, nullptr));
1192
+ }
1193
+ };
1194
+
1195
+ // Write init file header
1196
+ writeCpioHeader(initSize, SourcePath.filename().string().c_str());
1197
+
1198
+ // Copy file contents
1199
+ wsl::windows::common::relay::InterruptableRelay(sourceFile.get(), destFile.get(), nullptr, 64 * 1024);
1200
+
1201
+ // Write data padding
1202
+ if (initDataPadding > 0)
1203
+ {
1204
+ char padding[4] = {0};
1205
+ DWORD bytesWritten;
1206
+ THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), padding, static_cast<DWORD>(initDataPadding), &bytesWritten, nullptr));
1207
+ }
1208
+
1209
+ // Write trailer entry (empty file with name "TRAILER!!!")
1210
+ writeCpioHeader(0, "TRAILER!!!");
1211
+
1212
+ // Pad the archive to 512-byte boundary
1213
+ constexpr DWORD archiveBlockSize = 512;
1214
+ LARGE_INTEGER currentPos{};
1215
+ THROW_IF_WIN32_BOOL_FALSE(SetFilePointerEx(destFile.get(), {}, ¤tPos, FILE_CURRENT));
1216
+
1217
+ const auto archivePadding = (archiveBlockSize - (currentPos.LowPart % archiveBlockSize)) % archiveBlockSize;
1218
+ if (archivePadding > 0)
1219
+ {
1220
+ char paddingBuffer[archiveBlockSize] = {0};
1221
+ DWORD bytesWritten;
1222
+ THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), paddingBuffer, archivePadding, &bytesWritten, nullptr));
1223
+ }
1224
+
1225
+ deleteFile.release();
1226
+}
1227
+
1228
wil::unique_hfile wsl::windows::common::filesystem::WipeAndOpenDirectory(_In_ LPCWSTR pPath)
1229
{
1230
const auto result = wil::RemoveDirectoryRecursiveNoThrow(pPath);
src/windows/common/filesystem.hpp
+5
@@ -228,6 +228,11 @@ std::wstring UnquotePath(_In_ LPCWSTR Path);
228
/// </summary>
229
void UpdateInit(_In_ PCWSTR BasePath, _In_ ULONG DistroVersion);
230
231
+/// <summary>
232
+/// Creates a CPIO newc format initrd archive from a single source file.
233
+/// </summary>
234
+void CreateCpioInitrd(_In_ const std::filesystem::path& SourcePath, _In_ const std::filesystem::path& DestPath);
235
+
236
/// <summary>
237
/// Wipes out the directory with the given path if it exists, then creates it again and returns
238
/// an open directory handle onto it.
src/windows/service/exe/WslCoreVm.cpp
+1
-1
@@ -166,7 +166,7 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
166
// Set the install path of the package.
167
m_installPath = wsl::windows::common::wslutil::GetBasePath();
168
169
- // Initialize the path to the tools folder.
169
+ // Initialize the path to the tools folder which also serves as the default rootfs path.
170
m_rootFsPath = m_installPath / LXSS_TOOLS_DIRECTORY;
171
172
// Store the path of the user profile.
src/windows/wslinstall/DllMain.cpp
+53
-15
@@ -42,6 +42,12 @@ static constexpr auto c_wslSettingsProgIDPropertyName = L"WSLSETTINGSPROGID";
42
return NOERROR; \
43
}
44
45
+#define WSL_INSTALL_LOG(Name, ...) \
46
+ { \
47
+ WSL_LOG(Name, __VA_ARGS__); \
48
+ WriteInstallLog(std::format("MSI install: {}", Name)); \
49
+ }
50
+
51
#ifndef WSL_OFFICIAL_BUILD
52
void TrustPackageCertificate(LPCWSTR Path)
53
{
@@ -519,8 +525,7 @@ extern "C" UINT __stdcall CleanMsixState(MSIHANDLE install)
525
extern "C" UINT __stdcall DeprovisionMsix(MSIHANDLE install)
526
try
527
{
522
- WSL_LOG("DeprovisionMsix");
523
- WriteInstallLog("MSI install: DeprovisionMsix");
528
+ WSL_INSTALL_LOG("DeprovisionMsix");
529
530
const winrt::Windows::Management::Deployment::PackageManager packageManager;
531
const auto result = packageManager.DeprovisionPackageForAllUsersAsync(wsl::windows::common::wslutil::c_msixPackageFamilyName).get();
@@ -543,8 +548,7 @@ catch (...)
548
extern "C" UINT __stdcall RemoveMsixAsSystem(MSIHANDLE install)
549
try
550
{
546
- WSL_LOG("RemoveMsixAsSystem");
547
- WriteInstallLog("MSI install: RemoveMsixAsSystem");
551
+ WSL_INSTALL_LOG("RemoveMsixAsSystem");
552
553
const winrt::Windows::Management::Deployment::PackageManager packageManager;
554
@@ -573,8 +577,7 @@ catch (...)
577
extern "C" UINT __stdcall RemoveMsixAsUser(MSIHANDLE install)
578
try
579
{
576
- WSL_LOG("RemoveMsixAsUser");
577
- WriteInstallLog("MSI install: RemoveMsixAsUser");
580
+ WSL_INSTALL_LOG("RemoveMsixAsUser");
581
582
const winrt::Windows::Management::Deployment::PackageManager packageManager;
583
@@ -643,8 +646,7 @@ wsl::windows::common::filesystem::TempFile ExtractMsix(MSIHANDLE install)
646
extern "C" UINT __stdcall InstallMsixAsUser(MSIHANDLE install)
647
try
648
{
646
- WSL_LOG("InstallMsixAsUser");
647
- WriteInstallLog("MSI install: InstallMsixAsUser");
649
+ WSL_INSTALL_LOG("InstallMsixAsUser");
650
651
// RegisterPackageByFamilyNameAsync() cannot be run as SYSTEM.
652
// If this thread runs as SYSTEM, simply skip this step.
@@ -687,8 +689,7 @@ try
689
// Release a file handle to the MSIX file so that it can be installed.
690
msixFile.Handle.reset();
691
690
- WSL_LOG("InstallMsix", TraceLoggingValue(msixFile.Path.c_str(), "Path"));
691
- WriteInstallLog("MSI install: InstallMsix");
692
+ WSL_INSTALL_LOG("InstallMsix", TraceLoggingValue(msixFile.Path.c_str(), "Path"));
693
694
winrt::Windows::Management::Deployment::PackageManager packageManager;
695
@@ -790,8 +791,7 @@ extern "C" UINT __stdcall WslFinalizeInstallation(MSIHANDLE install)
791
{
792
try
793
{
793
- WSL_LOG("WslFinalizeInstallation");
794
- WriteInstallLog(std::format("MSI install: WslFinalizeInstallation"));
794
+ WSL_INSTALL_LOG("WslFinalizeInstallation");
795
}
796
CATCH_LOG();
797
@@ -801,9 +801,7 @@ extern "C" UINT __stdcall WslFinalizeInstallation(MSIHANDLE install)
801
extern "C" UINT __stdcall WslValidateInstallation(MSIHANDLE install)
802
try
803
{
804
- WSL_LOG("WslValidateInstallation");
805
-
806
- WriteInstallLog(std::format("MSI install: WslValidateInstallation"));
804
+ WSL_INSTALL_LOG("WslValidateInstallation");
805
806
// TODO: Use a more precise version check so we don't install if the Windows build doesn't support lifted.
807
@@ -874,6 +872,46 @@ extern "C" UINT __stdcall UnregisterLspCategories(MSIHANDLE install)
872
return NOERROR;
873
}
874
875
+extern "C" UINT __stdcall CreateInitrd(MSIHANDLE install)
876
+try
877
+{
878
+ WSL_INSTALL_LOG("CreateInitrd");
879
+
880
+ const auto installRoot = wsl::windows::common::wslutil::GetMsiPackagePath();
881
+ THROW_HR_IF(E_INVALIDARG, !installRoot.has_value());
882
+
883
+ const auto toolsPath = std::filesystem::path(installRoot.value()) / LXSS_TOOLS_DIRECTORY;
884
+ const auto initPath = toolsPath / L"init";
885
+ const auto initrdPath = toolsPath / LXSS_VM_MODE_INITRD_NAME;
886
+ wsl::windows::common::filesystem::CreateCpioInitrd(initPath, initrdPath);
887
+
888
+ return NOERROR;
889
+}
890
+catch (...)
891
+{
892
+ LOG_CAUGHT_EXCEPTION();
893
+
894
+ return ERROR_INSTALL_FAILURE;
895
+}
896
+
897
+extern "C" UINT __stdcall RemoveInitrd(MSIHANDLE install)
898
+{
899
+ try
900
+ {
901
+ WSL_INSTALL_LOG("RemoveInitrd");
902
+
903
+ const auto installRoot = wsl::windows::common::wslutil::GetMsiPackagePath();
904
+ THROW_HR_IF(E_INVALIDARG, !installRoot.has_value());
905
+
906
+ const auto initrdPath = std::filesystem::path(installRoot.value()) / LXSS_TOOLS_DIRECTORY / LXSS_VM_MODE_INITRD_NAME;
907
+ THROW_IF_WIN32_BOOL_FALSE(DeleteFileW(initrdPath.c_str()));
908
+ }
909
+ CATCH_LOG()
910
+
911
+ // Failures in this method aren't fatal.
912
+ return NOERROR;
913
+}
914
+
915
std::wstring GetWslSettingsInstalledExePath(MSIHANDLE install)
916
{
917
const auto wslSettingsInstallFolder = GetMsiProperty(install, c_wslSettingsInstalledDirectoryPropertyName);
src/windows/wslinstall/wslinstall.def
+2
@@ -4,6 +4,8 @@ EXPORTS
4
CleanExplorerState
5
CleanMsixState
6
DeprovisionMsix
7
+ CreateInitrd
8
+ RemoveInitrd
9
WslValidateInstallation
10
WslFinalizeInstallation
11
InstallMsix
test/windows/InstallerTests.cpp
+54
-3
@@ -285,9 +285,9 @@ class InstallerTests
285
}
286
}
287
288
- void ValidatePackageInstalledProperly()
288
+ void ValidatePackageInstalledProperly(LPCWSTR expectedVersion = WIDEN(WSL_PACKAGE_VERSION))
289
{
290
- ValidateInstalledVersion(WIDEN(WSL_PACKAGE_VERSION));
290
+ ValidateInstalledVersion(expectedVersion);
291
292
auto checkInstalled = []() {
293
auto commandLine = LxssGenerateWslCommandLine(L"echo OK");
@@ -322,7 +322,7 @@ class InstallerTests
322
const auto key = wsl::windows::common::registry::OpenLxssMachineKey();
323
const auto version = wsl::windows::common::registry::ReadString(key.get(), L"MSI", L"Version", L"");
324
325
- VERIFY_ARE_EQUAL(version, WIDEN(WSL_PACKAGE_VERSION));
325
+ VERIFY_ARE_EQUAL(version, expectedVersion);
326
VERIFY_IS_FALSE(GetMsiProductCode().empty());
327
328
// TODO: check wslsupport, wslapi and p9rdr
@@ -485,6 +485,57 @@ class InstallerTests
485
ValidatePackageInstalledProperly();
486
}
487
488
+ TEST_METHOD(InitrdImgLifecycle)
489
+ {
490
+ // initrd.img is generated during installation from the init binary.
491
+ // It should:
492
+ // 1. Exist after a fresh install (generated by custom action)
493
+ // 2. Be re-created when the component is upgraded
494
+ // 3. Persist when reinstalling the same version
495
+ // 4. Be removed on uninstall
496
+
497
+ auto initrdPath = m_installedPath / L"tools" / L"initrd.img";
498
+
499
+ // Uninstall and install an older version to get a clean state
500
+ UninstallMsi();
501
+ VERIFY_IS_FALSE(IsMsiPackageInstalled());
502
+
503
+ // Case 1: Install older version (2.5.10 which ships initrd.img in the MSI)
504
+ InstallGitHubRelease(L"2.5.10");
505
+ VERIFY_IS_TRUE(IsMsiPackageInstalled());
506
+ VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after installing 2.5.10");
507
+ ValidatePackageInstalledProperly(L"2.5.10.0");
508
+
509
+ // Case 2: Upgrade to current version - initrd.img should be regenerated by custom action
510
+ InstallMsi();
511
+ VERIFY_IS_TRUE(IsMsiPackageInstalled());
512
+ VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after upgrade (generated by installer)");
513
+ ValidatePackageInstalledProperly();
514
+
515
+ // Case 3: Reinstall same version - initrd.img should persist
516
+ InstallMsi();
517
+ VERIFY_IS_TRUE(IsMsiPackageInstalled());
518
+ VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after reinstalling same version");
519
+ ValidatePackageInstalledProperly();
520
+
521
+ // Case 4: Downgrade to older version
522
+ InstallGitHubRelease(L"2.5.10");
523
+ VERIFY_IS_TRUE(IsMsiPackageInstalled());
524
+ VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after downgrade");
525
+ ValidatePackageInstalledProperly(L"2.5.10.0");
526
+
527
+ // Case 5: Uninstall - initrd.img should be removed
528
+ UninstallMsi();
529
+ VERIFY_IS_FALSE(IsMsiPackageInstalled());
530
+ VERIFY_IS_FALSE(std::filesystem::exists(initrdPath), L"initrd.img should be removed on uninstall");
531
+
532
+ // Reinstall to leave the system in a consistent state for other tests
533
+ InstallMsi();
534
+ VERIFY_IS_TRUE(IsMsiPackageInstalled());
535
+ VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after fresh install");
536
+ ValidatePackageInstalledProperly();
537
+ }
538
+
539
TEST_METHOD(UpgradeInstallsTheMsiPackage)
540
{
541
// Remove the MSIX package
test/windows/SimpleTests.cpp
+55
@@ -296,5 +296,60 @@ class SimpleTests
296
VERIFY_IS_TRUE(output.find(L"/mnt/c/Program Files (x86)/Common Files") != std::wstring::npos);
297
VERIFY_IS_TRUE(output.find(L"/mnt/c/Users/Test User/AppData/Local/Programs/Microsoft VS Code/bin") != std::wstring::npos);
298
}
299
+
300
+ TEST_METHOD(CreateCpioInitrd)
301
+ {
302
+ using wsl::windows::common::filesystem::CreateCpioInitrd;
303
+ using wsl::windows::common::filesystem::TempFile;
304
+ using wsl::windows::common::filesystem::TempFileFlags;
305
+
306
+ auto validateCpio = [](size_t sourceSize) {
307
+ // Create source file with specified size
308
+ TempFile sourceFile(GENERIC_WRITE, 0, CREATE_ALWAYS, TempFileFlags::None);
309
+ std::vector<char> sourceData(sourceSize, 'X');
310
+ DWORD written;
311
+ THROW_IF_WIN32_BOOL_FALSE(
312
+ WriteFile(sourceFile.Handle.get(), sourceData.data(), static_cast<DWORD>(sourceData.size()), &written, nullptr));
313
+ sourceFile.Handle.reset();
314
+
315
+ // Create CPIO archive
316
+ TempFile destFile(0, 0, CREATE_ALWAYS, TempFileFlags::None, L"img");
317
+ CreateCpioInitrd(sourceFile.Path, destFile.Path);
318
+
319
+ // Read and validate the CPIO archive
320
+ wil::unique_hfile cpioHandle{CreateFileW(
321
+ destFile.Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
322
+ THROW_LAST_ERROR_IF(!cpioHandle);
323
+
324
+ LARGE_INTEGER cpioSize{};
325
+ THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(cpioHandle.get(), &cpioSize));
326
+ VERIFY_ARE_EQUAL(cpioSize.QuadPart % 512, 0LL); // Archive padded to 512-byte boundary
327
+
328
+ char header[110] = {};
329
+ DWORD bytesRead;
330
+ THROW_IF_WIN32_BOOL_FALSE(ReadFile(cpioHandle.get(), header, sizeof(header), &bytesRead, nullptr));
331
+ VERIFY_ARE_EQUAL(bytesRead, static_cast<DWORD>(sizeof(header)));
332
+
333
+ // Parse CPIO newc header: magic(6) ino mode uid gid nlink mtime filesize devmajor devminor rdevmajor rdevminor namesize check
334
+ DWORD fileSize, nameSize;
335
+ VERIFY_ARE_EQUAL(sscanf_s(header, "070701%*8x%*8x%*8x%*8x%*8x%*8x%8x%*8x%*8x%*8x%*8x%8x", &fileSize, &nameSize), 2);
336
+
337
+ // Verify filename matches source file
338
+ auto expectedName = sourceFile.Path.filename().string();
339
+ VERIFY_ARE_EQUAL(nameSize, static_cast<DWORD>(expectedName.size() + 1));
340
+
341
+ std::string filename(nameSize, '\0');
342
+ THROW_IF_WIN32_BOOL_FALSE(ReadFile(cpioHandle.get(), filename.data(), nameSize, &bytesRead, nullptr));
343
+ VERIFY_ARE_EQUAL(filename.c_str(), expectedName);
344
+
345
+ VERIFY_ARE_EQUAL(fileSize, static_cast<DWORD>(sourceSize));
346
+ };
347
+
348
+ // Test various sizes to exercise 4-byte alignment padding
349
+ for (size_t size : {0, 1, 2, 3, 4, 5, 100, 1024, 4096, 65536})
350
+ {
351
+ validateCpio(size);
352
+ }
353
+ }
354
};
355
} // namespace SimpleTests
\ No newline at end of file
tools/bin2cpio/CPIOImage.pm
deleted
-236
@@ -1,236 +0,0 @@
1
-#
2
-# Module to create CPIO packed strings.
3
-#
4
-# N.B. The library currently only supports minimal operations in the "New ASCII
5
-# Format" including:
6
-# +Adding files
7
-# +Adding trailer
8
-#
9
-
10
-package CPIOImage;
11
-
12
-use strict;
13
-use Math::BigInt qw/:constant/;
14
-use Exporter 'import';
15
-use File::Basename;
16
-use bytes;
17
-no bytes;
18
-
19
-our @EXPORT = qw(AddObject,
20
- AddTrailer,
21
- WriteFile
22
- );
23
-
24
-#
25
-# CPIO constants
26
-#
27
-use constant MAGIC_NEWC => "070701";
28
-use constant HEADER_ENTRY_FORMAT => "%08X";
29
-use constant CHECK => 0;
30
-use constant NAME_EXTRA_PADDING => 2;
31
-use constant HEADER_PADDING_SIZE => 4;
32
-use constant FILE_PADDING_SIZE => 512;
33
-
34
-use constant TRAILER => "TRAILER!!!";
35
-
36
-#
37
-# CPIO values assumed for now.
38
-#
39
-# N.B. These need to be dynamically generated if used outside of basic scenarios
40
-# like initramfs.
41
-#
42
-use constant INODE => 0;
43
-use constant MODE => oct("100755");
44
-use constant UID => 0;
45
-use constant GID => 0;
46
-use constant NLINK => 0;
47
-use constant DEVMAJOR => 0;
48
-use constant DEVMINOR => 0;
49
-use constant RDEVMAJOR => 0;
50
-use constant RDEVMINOR => 0;
51
-
52
-sub ErrorExit($$) {
53
- my $line_number = shift;
54
- my $error_message = shift;
55
-
56
- print STDERR "$0($line_number, 1): error LX1: $error_message\n";
57
- exit 255;
58
-}
59
-
60
-#
61
-# Routine Description:
62
-#
63
-# This routine computes padding.
64
-#
65
-# Arguments:
66
-#
67
-# size - Supplies the size to align.
68
-#
69
-# alignment - Supplies the alignment
70
-#
71
-# Return Value:
72
-#
73
-# The number of bytes to pad.
74
-#
75
-sub GetPadding($$) {
76
- my $Size = shift;
77
- my $Alignment = shift;
78
-
79
- my $Result = $Size % $Alignment;
80
- if ($Result != 0)
81
- {
82
- $Result = $Alignment - $Result;
83
- }
84
-
85
- return $Result;
86
-}
87
-
88
-#
89
-# Routine Description:
90
-#
91
-# This routine creates a CPIO entry.
92
-#
93
-# Arguments:
94
-#
95
-# name - Supplies the name.
96
-#
97
-# fileContentsRef - Supplies the file contents reference.
98
-#
99
-# fileSize - Supplies the file size.
100
-#
101
-# Return Value:
102
-#
103
-# None.
104
-#
105
-sub CreateEntry($$$) {
106
- my $name = shift;
107
- my $fileContentsRef = shift;
108
- my $fileSize = shift;
109
-
110
- # Write static values.
111
- my $cpioEntry = MAGIC_NEWC;
112
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, INODE);
113
- my $mode = sprintf(HEADER_ENTRY_FORMAT, MODE);
114
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, MODE);
115
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, UID);
116
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, GID);
117
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, NLINK);
118
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, time());
119
-
120
- # Write the file size
121
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, $fileSize);
122
-
123
- # Write static values.
124
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, DEVMAJOR);
125
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, DEVMINOR);
126
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, RDEVMAJOR);
127
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, RDEVMINOR);
128
-
129
- # Write the name size including the NULL terminator.
130
- my $nameSize = length($name) + 1;
131
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, $nameSize);
132
-
133
- # Write static value.
134
- $cpioEntry .= sprintf(HEADER_ENTRY_FORMAT, CHECK);
135
-
136
- # Write the name including the NULL terminator, padded out to a multiple of 4
137
- $cpioEntry .= $name."\0";
138
- $cpioEntry .= "\0" x (GetPadding($nameSize + NAME_EXTRA_PADDING, HEADER_PADDING_SIZE));
139
-
140
- # Write the contents, padded out to a multiple of 4
141
- $cpioEntry .= ${$fileContentsRef};
142
- $cpioEntry .= "\0" x GetPadding($fileSize, HEADER_PADDING_SIZE);
143
-
144
- return $cpioEntry;
145
-}
146
-
147
-#
148
-# Routine Description:
149
-#
150
-# This routine adds an object to the cpio image stream.
151
-#
152
-# N.B. Only files are currently supported.
153
-#
154
-# Arguments:
155
-#
156
-# cpioImage - Supplies the cpio image.
157
-#
158
-# path - Supplies the path of the object to add.
159
-#
160
-# Return Value:
161
-#
162
-# None
163
-#
164
-sub AddObject($$$) {
165
- my $cpioImageRef = shift;
166
- my $path = shift;
167
- my $name = shift;
168
-
169
- # Open and read the file
170
- my $INPUT_HANDLE;
171
- unless(open($INPUT_HANDLE, "<", $path)){ErrorExit(__LINE__, $!);}
172
- binmode($INPUT_HANDLE);
173
- local $/ = undef;
174
- my $fileContents = <$INPUT_HANDLE>;
175
- close($INPUT_HANDLE);
176
-
177
- # Check the size of the file
178
- my $fileSize;
179
- unless(defined($fileSize = -s $path)){ErrorExit(__LINE__, "Could not check file size at line $.");}
180
- my $fileContentsSize = bytes::length($fileContents);
181
- if ($fileSize != $fileContentsSize){ErrorExit(__LINE__, "Invalid file size at line $.");}
182
-
183
- # Write the entry
184
- ${$cpioImageRef} .= CreateEntry($name, \$fileContents, $fileSize);
185
-}
186
-
187
-#
188
-# Routine Description:
189
-#
190
-# This routine adds the trailer to the cpio image stream.
191
-#
192
-# Arguments:
193
-#
194
-# cpioImageRef - Supplies the cpio image reference.
195
-#
196
-# Return Value:
197
-#
198
-# None
199
-#
200
-sub AddTrailer($) {
201
- my $cpioImageRef = shift;
202
-
203
- # Write the entry
204
- my $fileContents = "";
205
- ${$cpioImageRef} .= CreateEntry(TRAILER, \$fileContents, 0);
206
-
207
- # Pad out the file to the block size
208
- my $size = bytes::length(${$cpioImageRef});
209
- ${$cpioImageRef} .= "\0" x GetPadding($size, FILE_PADDING_SIZE);
210
-}
211
-
212
-#
213
-# Routine Description:
214
-#
215
-# This routine writes the cpio image stream to a file.
216
-#
217
-# Arguments:
218
-#
219
-# cpioImageRef - Supplies the cpio image reference.
220
-#
221
-# name - Supplies the name of the file.
222
-#
223
-# Return Value:
224
-#
225
-# None
226
-#
227
-sub WriteFile($$) {
228
- my $cpioImageRef = shift;
229
- my $name = shift;
230
-
231
- my $OUTPUT_HANDLE;
232
- unless(open($OUTPUT_HANDLE, ">", $name)){ErrorExit(__LINE__, $!);}
233
- binmode($OUTPUT_HANDLE);
234
- print $OUTPUT_HANDLE ${$cpioImageRef};
235
- close($OUTPUT_HANDLE);
236
-}
\ No newline at end of file
tools/bin2cpio/bin2cpio.pl
deleted
-30
@@ -1,30 +0,0 @@
1
-#
2
-# Script to convert a binary to a CPIO image.
3
-#
4
-
5
-use strict;
6
-use File::Basename;
7
-use lib dirname (__FILE__);
8
-use CPIOImage;
9
-
10
-if($#ARGV + 1 != 2)
11
-{
12
- ErrorExit(__LINE__, "Invalid command line at line $.");
13
-}
14
-
15
-my $inputFile = $ARGV[0];
16
-my $outputFile = $ARGV[1];
17
-
18
-my $cpioStream = "";
19
-CPIOImage::AddObject(\$cpioStream, $inputFile, "init");
20
-CPIOImage::AddTrailer(\$cpioStream);
21
-CPIOImage::WriteFile(\$cpioStream, $outputFile);
22
-exit 0;
23
-
24
-sub ErrorExit($$) {
25
- my $line_number = shift;
26
- my $error_message = shift;
27
-
28
- print STDERR "$0($line_number, 1): error LX2: $error_message\n";
29
- exit 255;
30
-}
\ No newline at end of file