1 /* 2 * PROJECT: ReactOS Setup Library 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: NT 5.x family (MS Windows <= 2003, and ReactOS) 5 * operating systems detection code. 6 * COPYRIGHT: Copyright 2017-2024 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 7 */ 8 9 /* INCLUDES *****************************************************************/ 10 11 #include "precomp.h" 12 13 #include "ntverrsrc.h" 14 // #include "arcname.h" 15 #include "bldrsup.h" 16 #include "filesup.h" 17 #include "genlist.h" 18 #include "partlist.h" 19 #include "arcname.h" 20 #include "osdetect.h" 21 22 #define NDEBUG 23 #include <debug.h> 24 25 26 /* GLOBALS ******************************************************************/ 27 28 /* Language-independent Vendor strings */ 29 static const PCWSTR KnownVendors[] = { VENDOR_REACTOS, VENDOR_MICROSOFT }; 30 31 32 /* FUNCTIONS ****************************************************************/ 33 34 static BOOLEAN 35 IsValidNTOSInstallation( 36 IN PUNICODE_STRING SystemRootPath, 37 OUT PUSHORT Machine OPTIONAL, 38 OUT PUNICODE_STRING VendorName OPTIONAL); 39 40 static PNTOS_INSTALLATION 41 FindExistingNTOSInstall( 42 IN PGENERIC_LIST List, 43 IN PCWSTR SystemRootArcPath OPTIONAL, 44 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ? 45 ); 46 47 static PNTOS_INSTALLATION 48 AddNTOSInstallation( 49 _In_ PGENERIC_LIST List, 50 _In_ PCWSTR InstallationName, 51 _In_ USHORT Machine, 52 _In_ PCWSTR VendorName, 53 _In_ PCWSTR SystemRootArcPath, 54 _In_ PUNICODE_STRING SystemRootNtPath, // or PCWSTR ? 55 _In_ PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer 56 _In_ ULONG DiskNumber, 57 _In_ ULONG PartitionNumber); 58 59 typedef struct _ENUM_INSTALLS_DATA 60 { 61 _Inout_ PGENERIC_LIST List; 62 _In_ PPARTLIST PartList; 63 } ENUM_INSTALLS_DATA, *PENUM_INSTALLS_DATA; 64 65 // PENUM_BOOT_ENTRIES_ROUTINE 66 static NTSTATUS 67 NTAPI 68 EnumerateInstallations( 69 IN BOOT_STORE_TYPE Type, 70 IN PBOOT_STORE_ENTRY BootEntry, 71 IN PVOID Parameter OPTIONAL) 72 { 73 PENUM_INSTALLS_DATA Data = (PENUM_INSTALLS_DATA)Parameter; 74 PNTOS_OPTIONS Options = (PNTOS_OPTIONS)&BootEntry->OsOptions; 75 PNTOS_INSTALLATION NtOsInstall; 76 77 ULONG DiskNumber = 0, PartitionNumber = 0; 78 PCWSTR PathComponent = NULL; 79 80 UNICODE_STRING SystemRootPath; 81 WCHAR SystemRoot[MAX_PATH]; 82 83 USHORT Machine; 84 UNICODE_STRING VendorName; 85 WCHAR VendorNameBuffer[MAX_PATH]; 86 87 88 /* We have a boot entry */ 89 90 /* Check for supported boot type "Windows2003" */ 91 if (BootEntry->OsOptionsLength < sizeof(NTOS_OPTIONS) || 92 RtlCompareMemory(&BootEntry->OsOptions /* Signature */, 93 NTOS_OPTIONS_SIGNATURE, 94 RTL_FIELD_SIZE(NTOS_OPTIONS, Signature)) != 95 RTL_FIELD_SIZE(NTOS_OPTIONS, Signature)) 96 { 97 /* This is not a ReactOS entry */ 98 // DPRINT(" An installation '%S' of unsupported type '%S'\n", 99 // BootEntry->FriendlyName, BootEntry->Version ? BootEntry->Version : L"n/a"); 100 DPRINT(" An installation '%S' of unsupported type %lu\n", 101 BootEntry->FriendlyName, BootEntry->OsOptionsLength); 102 /* Continue the enumeration */ 103 return STATUS_SUCCESS; 104 } 105 106 /* BootType is Windows2003, now check OsLoadPath */ 107 if (!Options->OsLoadPath || !*Options->OsLoadPath) 108 { 109 /* Certainly not a ReactOS installation */ 110 DPRINT1(" A Win2k3 install '%S' without an ARC path?!\n", BootEntry->FriendlyName); 111 /* Continue the enumeration */ 112 return STATUS_SUCCESS; 113 } 114 115 DPRINT(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n", 116 BootEntry->FriendlyName, Options->OsLoadPath); 117 // DPRINT(" Found a Win2k3 install '%S' with ARC path '%S'\n", 118 // BootEntry->FriendlyName, Options->OsLoadPath); 119 120 // TODO: Normalize the ARC path. 121 122 /* 123 * Check whether we already have an installation with this ARC path. 124 * If this is the case, stop there. 125 */ 126 NtOsInstall = FindExistingNTOSInstall(Data->List, Options->OsLoadPath, NULL); 127 if (NtOsInstall) 128 { 129 DPRINT(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n", 130 NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemArcPath); 131 /* Continue the enumeration */ 132 return STATUS_SUCCESS; 133 } 134 135 /* 136 * Convert the ARC path into an NT path, from which we will deduce the 137 * real disk & partition on which the candidate installation resides, 138 * as well as verifying whether it is indeed an NTOS installation. 139 */ 140 RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot)); 141 if (!ArcPathToNtPath(&SystemRootPath, Options->OsLoadPath, Data->PartList)) 142 { 143 DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", Options->OsLoadPath); 144 /* Continue the enumeration */ 145 return STATUS_SUCCESS; 146 } 147 148 DPRINT("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n", 149 Options->OsLoadPath, &SystemRootPath); 150 151 /* 152 * Check whether we already have an installation with this NT path. 153 * If this is the case, stop there. 154 */ 155 NtOsInstall = FindExistingNTOSInstall(Data->List, NULL /*Options->OsLoadPath*/, &SystemRootPath); 156 if (NtOsInstall) 157 { 158 DPRINT1(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n", 159 NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemNtPath); 160 /* Continue the enumeration */ 161 return STATUS_SUCCESS; 162 } 163 164 DPRINT("EnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath); 165 166 /* Check if this is a valid NTOS installation; stop there if it isn't one */ 167 RtlInitEmptyUnicodeString(&VendorName, VendorNameBuffer, sizeof(VendorNameBuffer)); 168 if (!IsValidNTOSInstallation(&SystemRootPath, &Machine, &VendorName)) 169 { 170 /* Continue the enumeration */ 171 return STATUS_SUCCESS; 172 } 173 174 DPRINT("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", 175 Options->OsLoadPath, &SystemRootPath); 176 177 /* From the NT path, compute the disk, partition and path components */ 178 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent)) 179 { 180 DPRINT("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n", 181 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent); 182 } 183 else 184 { 185 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath); 186 } 187 188 /* Add the discovered NTOS installation into the list */ 189 NtOsInstall = AddNTOSInstallation(Data->List, 190 BootEntry->FriendlyName, 191 Machine, 192 VendorName.Buffer, // FIXME: What if it's not NULL-terminated? 193 Options->OsLoadPath, 194 &SystemRootPath, PathComponent, 195 DiskNumber, PartitionNumber); 196 if (NtOsInstall) 197 { 198 /* Retrieve the volume corresponding to the disk and partition numbers */ 199 PPARTENTRY PartEntry = SelectPartition(Data->PartList, DiskNumber, PartitionNumber); 200 if (!PartEntry) 201 { 202 DPRINT1("SelectPartition(disk #%d, partition #%d) failed\n", 203 DiskNumber, PartitionNumber); 204 } 205 NtOsInstall->Volume = (PartEntry ? PartEntry->Volume : NULL); 206 } 207 208 /* Continue the enumeration */ 209 return STATUS_SUCCESS; 210 } 211 212 /** 213 * @brief 214 * Finds the first occurrence of a sub-string 'strSearch' inside 'str', 215 * using case-insensitive comparisons. 216 **/ 217 PCWSTR 218 NTAPI 219 FindSubStrI( 220 _In_ PCWSTR str, 221 _In_ PCWSTR strSearch) 222 { 223 PCWSTR cp = str; 224 PCWSTR s1, s2; 225 226 if (!*strSearch) 227 return str; 228 229 while (*cp) 230 { 231 s1 = cp; 232 s2 = strSearch; 233 234 while (*s1 && *s2 && (towupper(*s1) == towupper(*s2))) 235 ++s1, ++s2; 236 237 if (!*s2) 238 return cp; 239 240 ++cp; 241 } 242 243 return NULL; 244 } 245 246 static BOOLEAN 247 CheckForValidPEAndVendor( 248 IN HANDLE RootDirectory OPTIONAL, 249 IN PCWSTR PathNameToFile, 250 OUT PUSHORT Machine, 251 OUT PUNICODE_STRING VendorName) 252 { 253 BOOLEAN Success = FALSE; 254 NTSTATUS Status; 255 HANDLE FileHandle, SectionHandle; 256 // SIZE_T ViewSize; 257 PVOID ViewBase; 258 PIMAGE_NT_HEADERS NtHeader; 259 PVOID VersionBuffer = NULL; // Read-only 260 PVOID pvData = NULL; 261 UINT BufLen = 0; 262 263 if (VendorName->MaximumLength < sizeof(UNICODE_NULL)) 264 return FALSE; 265 266 *VendorName->Buffer = UNICODE_NULL; 267 VendorName->Length = 0; 268 269 Status = OpenAndMapFile(RootDirectory, PathNameToFile, 270 &FileHandle, NULL, 271 &SectionHandle, &ViewBase, FALSE); 272 if (!NT_SUCCESS(Status)) 273 { 274 DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status); 275 return FALSE; // Status; 276 } 277 278 /* Make sure it's a valid NT PE file */ 279 NtHeader = RtlImageNtHeader(ViewBase); 280 if (!NtHeader) 281 { 282 DPRINT1("File '%S' does not seem to be a valid NT PE file, bail out\n", PathNameToFile); 283 Status = STATUS_INVALID_IMAGE_FORMAT; 284 goto UnmapCloseFile; 285 } 286 287 /* Retrieve the target architecture of this PE module */ 288 *Machine = NtHeader->FileHeader.Machine; 289 290 /* 291 * Search for a valid executable version and vendor. 292 * NOTE: The module is loaded as a data file, it should be marked as such. 293 */ 294 Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL); 295 if (!NT_SUCCESS(Status)) 296 { 297 DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", PathNameToFile, Status); 298 goto UnmapCloseFile; 299 } 300 301 Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen); 302 if (NT_SUCCESS(Status)) 303 { 304 USHORT wCodePage = 0, wLangID = 0; 305 WCHAR FileInfo[MAX_PATH]; 306 307 wCodePage = LOWORD(*(ULONG*)pvData); 308 wLangID = HIWORD(*(ULONG*)pvData); 309 310 RtlStringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo), 311 L"StringFileInfo\\%04X%04X\\CompanyName", 312 wCodePage, wLangID); 313 314 Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen); 315 316 /* Fixup the Status in case pvData is NULL */ 317 if (NT_SUCCESS(Status) && !pvData) 318 Status = STATUS_NOT_FOUND; 319 320 if (NT_SUCCESS(Status) /*&& pvData*/) 321 { 322 /* BufLen includes the NULL terminator count */ 323 DPRINT("Found version vendor: \"%S\" for file '%S'\n", pvData, PathNameToFile); 324 325 RtlStringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength, 326 pvData, BufLen * sizeof(WCHAR)); 327 VendorName->Length = (USHORT)wcslen(VendorName->Buffer) * sizeof(WCHAR); 328 329 Success = TRUE; 330 } 331 } 332 333 if (!NT_SUCCESS(Status)) 334 DPRINT("No version vendor found for file '%S'\n", PathNameToFile); 335 336 UnmapCloseFile: 337 /* Finally, unmap and close the file */ 338 UnMapAndCloseFile(FileHandle, SectionHandle, ViewBase); 339 340 return Success; 341 } 342 343 // 344 // TODO: Instead of returning TRUE/FALSE, it would be nice to return 345 // a flag indicating: 346 // - whether the installation is actually valid; 347 // - if it's broken or not (aka. needs for repair, or just upgrading). 348 // 349 static BOOLEAN 350 IsValidNTOSInstallationByHandle( 351 IN HANDLE SystemRootDirectory, 352 OUT PUSHORT Machine OPTIONAL, 353 OUT PUNICODE_STRING VendorName OPTIONAL) 354 { 355 BOOLEAN Success = FALSE; 356 PCWSTR PathName; 357 USHORT i; 358 USHORT LocalMachine; 359 UNICODE_STRING LocalVendorName; 360 WCHAR VendorNameBuffer[MAX_PATH]; 361 362 /* Check for VendorName validity */ 363 if (VendorName->MaximumLength < sizeof(UNICODE_NULL)) 364 { 365 /* Don't use it, invalidate the pointer */ 366 VendorName = NULL; 367 } 368 else 369 { 370 /* Zero it out */ 371 *VendorName->Buffer = UNICODE_NULL; 372 VendorName->Length = 0; 373 } 374 375 /* Check for the existence of \SystemRoot\System32 */ 376 PathName = L"System32\\"; 377 if (!DoesDirExist(SystemRootDirectory, PathName)) 378 { 379 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 380 return FALSE; 381 } 382 383 /* Check for the existence of \SystemRoot\System32\drivers */ 384 PathName = L"System32\\drivers\\"; 385 if (!DoesDirExist(SystemRootDirectory, PathName)) 386 { 387 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 388 return FALSE; 389 } 390 391 /* Check for the existence of \SystemRoot\System32\config */ 392 PathName = L"System32\\config\\"; 393 if (!DoesDirExist(SystemRootDirectory, PathName)) 394 { 395 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 396 return FALSE; 397 } 398 399 #if 0 400 /* 401 * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config 402 * (but we don't check here whether they are actually valid). 403 */ 404 PathName = L"System32\\config\\SYSTEM"; 405 if (!DoesFileExist(SystemRootDirectory, PathName)) 406 { 407 // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status); 408 return FALSE; 409 } 410 PathName = L"System32\\config\\SOFTWARE"; 411 if (!DoesFileExist(SystemRootDirectory, PathName)) 412 { 413 // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status); 414 return FALSE; 415 } 416 #endif 417 418 RtlInitEmptyUnicodeString(&LocalVendorName, VendorNameBuffer, sizeof(VendorNameBuffer)); 419 420 /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */ 421 PathName = L"System32\\ntoskrnl.exe"; 422 Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName); 423 if (!Success) 424 DPRINT1("Kernel executable '%S' is either not a PE file, or does not have any vendor?\n", PathName); 425 426 /* 427 * The kernel gives the OS its flavour. If we failed due to the absence of 428 * ntoskrnl.exe this might be due to the fact this particular installation 429 * uses a custom kernel that has a different name, overridden in the boot 430 * parameters. We then rely on the existence of ntdll.dll, which cannot be 431 * renamed on a valid NT system. 432 */ 433 if (Success) 434 { 435 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) 436 { 437 Success = !!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]); 438 if (Success) 439 { 440 /* We have found a correct vendor combination */ 441 DPRINT("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]); 442 break; 443 } 444 } 445 446 /* Return the target architecture */ 447 if (Machine) 448 { 449 /* Copy the value and invalidate the pointer */ 450 *Machine = LocalMachine; 451 Machine = NULL; 452 } 453 454 /* Return the vendor name */ 455 if (VendorName) 456 { 457 /* Copy the string and invalidate the pointer */ 458 RtlCopyUnicodeString(VendorName, &LocalVendorName); 459 VendorName = NULL; 460 } 461 } 462 463 /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */ 464 465 /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */ 466 PathName = L"System32\\ntdll.dll"; 467 Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName); 468 if (!Success) 469 DPRINT1("User-mode DLL '%S' is either not a PE file, or does not have any vendor?\n", PathName); 470 471 if (Success) 472 { 473 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) 474 { 475 if (!!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i])) 476 { 477 /* We have found a correct vendor combination */ 478 DPRINT("IsValidNTOSInstallation: The user-mode DLL '%S' is from %S\n", PathName, KnownVendors[i]); 479 break; 480 } 481 } 482 483 /* Return the target architecture if not already obtained */ 484 if (Machine) 485 { 486 /* Copy the value and invalidate the pointer */ 487 *Machine = LocalMachine; 488 Machine = NULL; 489 } 490 491 /* Return the vendor name if not already obtained */ 492 if (VendorName) 493 { 494 /* Copy the string and invalidate the pointer */ 495 RtlCopyUnicodeString(VendorName, &LocalVendorName); 496 VendorName = NULL; 497 } 498 } 499 500 return Success; 501 } 502 503 static BOOLEAN 504 IsValidNTOSInstallation( 505 IN PUNICODE_STRING SystemRootPath, 506 OUT PUSHORT Machine, 507 OUT PUNICODE_STRING VendorName OPTIONAL) 508 { 509 NTSTATUS Status; 510 OBJECT_ATTRIBUTES ObjectAttributes; 511 IO_STATUS_BLOCK IoStatusBlock; 512 HANDLE SystemRootDirectory; 513 BOOLEAN Success; 514 515 /* Open SystemRootPath */ 516 InitializeObjectAttributes(&ObjectAttributes, 517 SystemRootPath, 518 OBJ_CASE_INSENSITIVE, 519 NULL, 520 NULL); 521 Status = NtOpenFile(&SystemRootDirectory, 522 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, 523 &ObjectAttributes, 524 &IoStatusBlock, 525 FILE_SHARE_READ | FILE_SHARE_WRITE, 526 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); 527 if (!NT_SUCCESS(Status)) 528 { 529 DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", SystemRootPath, Status); 530 return FALSE; 531 } 532 533 Success = IsValidNTOSInstallationByHandle(SystemRootDirectory, 534 Machine, VendorName); 535 536 /* Done! */ 537 NtClose(SystemRootDirectory); 538 return Success; 539 } 540 541 #ifndef NDEBUG 542 static VOID 543 DumpNTOSInstalls( 544 IN PGENERIC_LIST List) 545 { 546 PGENERIC_LIST_ENTRY Entry; 547 PNTOS_INSTALLATION NtOsInstall; 548 ULONG NtOsInstallsCount = GetNumberOfListEntries(List); 549 550 DPRINT("There %s %d installation%s detected:\n", 551 NtOsInstallsCount >= 2 ? "are" : "is", 552 NtOsInstallsCount, 553 NtOsInstallsCount >= 2 ? "s" : ""); 554 555 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry)) 556 { 557 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry); 558 559 DPRINT(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n", 560 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, 561 NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath); 562 } 563 564 DPRINT("Done.\n"); 565 } 566 #endif 567 568 static PNTOS_INSTALLATION 569 FindExistingNTOSInstall( 570 IN PGENERIC_LIST List, 571 IN PCWSTR SystemRootArcPath OPTIONAL, 572 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ? 573 ) 574 { 575 PGENERIC_LIST_ENTRY Entry; 576 PNTOS_INSTALLATION NtOsInstall; 577 UNICODE_STRING SystemArcPath; 578 579 /* 580 * We search either via ARC path or NT path. 581 * If both pointers are NULL then we fail straight away. 582 */ 583 if (!SystemRootArcPath && !SystemRootNtPath) 584 return NULL; 585 586 RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath); 587 588 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry)) 589 { 590 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry); 591 592 /* 593 * Note that if both ARC paths are equal, then the corresponding 594 * NT paths must be the same. However, two ARC paths may be different 595 * but resolve into the same NT path. 596 */ 597 if ( (SystemRootArcPath && 598 RtlEqualUnicodeString(&NtOsInstall->SystemArcPath, 599 &SystemArcPath, TRUE)) || 600 (SystemRootNtPath && 601 RtlEqualUnicodeString(&NtOsInstall->SystemNtPath, 602 SystemRootNtPath, TRUE)) ) 603 { 604 /* Found it! */ 605 return NtOsInstall; 606 } 607 } 608 609 return NULL; 610 } 611 612 static PNTOS_INSTALLATION 613 AddNTOSInstallation( 614 _In_ PGENERIC_LIST List, 615 _In_ PCWSTR InstallationName, 616 _In_ USHORT Machine, 617 _In_ PCWSTR VendorName, 618 _In_ PCWSTR SystemRootArcPath, 619 _In_ PUNICODE_STRING SystemRootNtPath, // or PCWSTR ? 620 _In_ PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer 621 _In_ ULONG DiskNumber, 622 _In_ ULONG PartitionNumber) 623 { 624 PNTOS_INSTALLATION NtOsInstall; 625 SIZE_T ArcPathLength, NtPathLength; 626 627 /* Is there already any installation with these settings? */ 628 NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath); 629 if (NtOsInstall) 630 { 631 DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n", 632 NtOsInstall->InstallationName, NtOsInstall->VendorName, 633 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath); 634 // 635 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE... 636 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file. 637 // 638 return NtOsInstall; 639 } 640 641 ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR); 642 // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR)); 643 NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL); 644 645 /* None was found, so add a new one */ 646 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, 647 sizeof(*NtOsInstall) + 648 ArcPathLength + NtPathLength); 649 if (!NtOsInstall) 650 return NULL; 651 652 NtOsInstall->DiskNumber = DiskNumber; 653 NtOsInstall->PartitionNumber = PartitionNumber; 654 NtOsInstall->Machine = Machine; 655 656 RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath, 657 (PWCHAR)(NtOsInstall + 1), 658 ArcPathLength); 659 RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength); 660 NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL); 661 662 RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath, 663 (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength), 664 NtPathLength); 665 RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath); 666 NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer + 667 (PathComponent - SystemRootNtPath->Buffer); 668 669 RtlStringCchCopyW(NtOsInstall->InstallationName, 670 ARRAYSIZE(NtOsInstall->InstallationName), 671 InstallationName); 672 673 RtlStringCchCopyW(NtOsInstall->VendorName, 674 ARRAYSIZE(NtOsInstall->VendorName), 675 VendorName); 676 677 AppendGenericListEntry(List, NtOsInstall, FALSE); 678 679 return NtOsInstall; 680 } 681 682 static VOID 683 FindNTOSInstallations( 684 _Inout_ PGENERIC_LIST List, 685 _In_ PPARTLIST PartList, 686 _In_ PVOLENTRY Volume) 687 { 688 NTSTATUS Status; 689 HANDLE VolumeRootDirHandle; 690 OBJECT_ATTRIBUTES ObjectAttributes; 691 IO_STATUS_BLOCK IoStatusBlock; 692 UNICODE_STRING VolumeRootPath; 693 BOOT_STORE_TYPE Type; 694 PVOID BootStoreHandle; 695 ENUM_INSTALLS_DATA Data; 696 ULONG Version; 697 WCHAR PathBuffer[RTL_NUMBER_OF_FIELD(VOLINFO, DeviceName) + 1]; 698 699 /* Set VolumeRootPath */ 700 RtlStringCchPrintfW(PathBuffer, _countof(PathBuffer), 701 L"%s\\", Volume->Info.DeviceName); 702 RtlInitUnicodeString(&VolumeRootPath, PathBuffer); 703 DPRINT("FindNTOSInstallations(%wZ)\n", &VolumeRootPath); 704 705 /* Open the volume */ 706 InitializeObjectAttributes(&ObjectAttributes, 707 &VolumeRootPath, 708 OBJ_CASE_INSENSITIVE, 709 NULL, 710 NULL); 711 Status = NtOpenFile(&VolumeRootDirHandle, 712 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, 713 &ObjectAttributes, 714 &IoStatusBlock, 715 FILE_SHARE_READ | FILE_SHARE_WRITE, 716 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); 717 if (!NT_SUCCESS(Status)) 718 { 719 DPRINT1("Failed to open volume '%wZ', Status 0x%08lx\n", &VolumeRootPath, Status); 720 return; 721 } 722 723 Data.List = List; 724 Data.PartList = PartList; 725 726 /* Try to see whether we recognize some NT boot loaders */ 727 for (Type = FreeLdr; Type < BldrTypeMax; ++Type) 728 { 729 Status = FindBootStore(VolumeRootDirHandle, Type, &Version); 730 if (!NT_SUCCESS(Status)) 731 { 732 /* The loader does not exist, continue with another one */ 733 DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n", 734 Type, Status); 735 continue; 736 } 737 738 /* The loader exists, try to enumerate its boot entries */ 739 DPRINT("Analyze the OS installations for loader type '%d' in Volume %wZ (Disk #%d, Partition #%d)\n", 740 Type, &VolumeRootPath, 741 Volume->PartEntry->DiskEntry->DiskNumber, 742 Volume->PartEntry->PartitionNumber); 743 744 Status = OpenBootStoreByHandle(&BootStoreHandle, VolumeRootDirHandle, Type, 745 BS_OpenExisting, BS_ReadAccess); 746 if (!NT_SUCCESS(Status)) 747 { 748 DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n", 749 Type, Status); 750 continue; 751 } 752 EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data); 753 CloseBootStore(BootStoreHandle); 754 } 755 756 /* Close the volume */ 757 NtClose(VolumeRootDirHandle); 758 } 759 760 /** 761 * @brief 762 * Create a list of available NT OS installations on the computer, 763 * by searching for recognized ones on each recognized storage volume. 764 **/ 765 // EnumerateNTOSInstallations 766 PGENERIC_LIST 767 NTAPI 768 CreateNTOSInstallationsList( 769 _In_ PPARTLIST PartList) 770 { 771 PGENERIC_LIST List; 772 PLIST_ENTRY Entry; 773 PVOLENTRY Volume; 774 BOOLEAN CheckVolume; 775 776 List = CreateGenericList(); 777 if (!List) 778 return NULL; 779 780 /* Loop each available volume */ 781 for (Entry = PartList->VolumesList.Flink; 782 Entry != &PartList->VolumesList; 783 Entry = Entry->Flink) 784 { 785 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 786 /* Valid OS installations can be found only on basic volumes */ 787 if (!Volume->PartEntry) // TODO: In the future: (!Volume->IsSimpleVolume) 788 continue; 789 790 CheckVolume = (!Volume->New && (Volume->FormatState == Formatted)); 791 792 #ifndef NDEBUG 793 { 794 PPARTENTRY PartEntry = Volume->PartEntry; 795 ASSERT(PartEntry->Volume == Volume); 796 DPRINT("Volume %S (%c%c) on Disk #%d, Partition #%d (%s), " 797 "index %d - Type 0x%02x, IsVolNew = %s, FormatState = %lu -- Should I check it? %s\n", 798 Volume->Info.DeviceName, 799 !Volume->Info.DriveLetter ? '-' : (CHAR)Volume->Info.DriveLetter, 800 !Volume->Info.DriveLetter ? '-' : ':', 801 PartEntry->DiskEntry->DiskNumber, 802 PartEntry->PartitionNumber, 803 PartEntry->LogicalPartition ? "Logical" : "Primary", 804 PartEntry->PartitionIndex, 805 PartEntry->PartitionType, 806 Volume->New ? "Yes" : "No", 807 Volume->FormatState, 808 CheckVolume ? "YES!" : "NO!"); 809 } 810 #endif 811 812 if (CheckVolume) 813 FindNTOSInstallations(List, PartList, Volume); 814 } 815 816 #ifndef NDEBUG 817 /**** Debugging: List all the collected installations ****/ 818 DumpNTOSInstalls(List); 819 #endif 820 821 return List; 822 } 823 824 /* EOF */ 825