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 * FindSubStrI(PCWSTR str, PCWSTR strSearch) : 214 * Searches for a sub-string 'strSearch' inside 'str', similarly to what 215 * wcsstr(str, strSearch) does, but ignores the case during the comparisons. 216 */ 217 PCWSTR FindSubStrI(PCWSTR str, PCWSTR strSearch) 218 { 219 PCWSTR cp = str; 220 PCWSTR s1, s2; 221 222 if (!*strSearch) 223 return str; 224 225 while (*cp) 226 { 227 s1 = cp; 228 s2 = strSearch; 229 230 while (*s1 && *s2 && (towupper(*s1) == towupper(*s2))) 231 ++s1, ++s2; 232 233 if (!*s2) 234 return cp; 235 236 ++cp; 237 } 238 239 return NULL; 240 } 241 242 static BOOLEAN 243 CheckForValidPEAndVendor( 244 IN HANDLE RootDirectory OPTIONAL, 245 IN PCWSTR PathNameToFile, 246 OUT PUSHORT Machine, 247 OUT PUNICODE_STRING VendorName) 248 { 249 BOOLEAN Success = FALSE; 250 NTSTATUS Status; 251 HANDLE FileHandle, SectionHandle; 252 // SIZE_T ViewSize; 253 PVOID ViewBase; 254 PIMAGE_NT_HEADERS NtHeader; 255 PVOID VersionBuffer = NULL; // Read-only 256 PVOID pvData = NULL; 257 UINT BufLen = 0; 258 259 if (VendorName->MaximumLength < sizeof(UNICODE_NULL)) 260 return FALSE; 261 262 *VendorName->Buffer = UNICODE_NULL; 263 VendorName->Length = 0; 264 265 Status = OpenAndMapFile(RootDirectory, PathNameToFile, 266 &FileHandle, NULL, 267 &SectionHandle, &ViewBase, FALSE); 268 if (!NT_SUCCESS(Status)) 269 { 270 DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status); 271 return FALSE; // Status; 272 } 273 274 /* Make sure it's a valid NT PE file */ 275 NtHeader = RtlImageNtHeader(ViewBase); 276 if (!NtHeader) 277 { 278 DPRINT1("File '%S' does not seem to be a valid NT PE file, bail out\n", PathNameToFile); 279 Status = STATUS_INVALID_IMAGE_FORMAT; 280 goto UnmapCloseFile; 281 } 282 283 /* Retrieve the target architecture of this PE module */ 284 *Machine = NtHeader->FileHeader.Machine; 285 286 /* 287 * Search for a valid executable version and vendor. 288 * NOTE: The module is loaded as a data file, it should be marked as such. 289 */ 290 Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL); 291 if (!NT_SUCCESS(Status)) 292 { 293 DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", PathNameToFile, Status); 294 goto UnmapCloseFile; 295 } 296 297 Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen); 298 if (NT_SUCCESS(Status)) 299 { 300 USHORT wCodePage = 0, wLangID = 0; 301 WCHAR FileInfo[MAX_PATH]; 302 303 wCodePage = LOWORD(*(ULONG*)pvData); 304 wLangID = HIWORD(*(ULONG*)pvData); 305 306 RtlStringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo), 307 L"StringFileInfo\\%04X%04X\\CompanyName", 308 wCodePage, wLangID); 309 310 Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen); 311 312 /* Fixup the Status in case pvData is NULL */ 313 if (NT_SUCCESS(Status) && !pvData) 314 Status = STATUS_NOT_FOUND; 315 316 if (NT_SUCCESS(Status) /*&& pvData*/) 317 { 318 /* BufLen includes the NULL terminator count */ 319 DPRINT("Found version vendor: \"%S\" for file '%S'\n", pvData, PathNameToFile); 320 321 RtlStringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength, 322 pvData, BufLen * sizeof(WCHAR)); 323 VendorName->Length = (USHORT)wcslen(VendorName->Buffer) * sizeof(WCHAR); 324 325 Success = TRUE; 326 } 327 } 328 329 if (!NT_SUCCESS(Status)) 330 DPRINT("No version vendor found for file '%S'\n", PathNameToFile); 331 332 UnmapCloseFile: 333 /* Finally, unmap and close the file */ 334 UnMapAndCloseFile(FileHandle, SectionHandle, ViewBase); 335 336 return Success; 337 } 338 339 // 340 // TODO: Instead of returning TRUE/FALSE, it would be nice to return 341 // a flag indicating: 342 // - whether the installation is actually valid; 343 // - if it's broken or not (aka. needs for repair, or just upgrading). 344 // 345 static BOOLEAN 346 IsValidNTOSInstallationByHandle( 347 IN HANDLE SystemRootDirectory, 348 OUT PUSHORT Machine OPTIONAL, 349 OUT PUNICODE_STRING VendorName OPTIONAL) 350 { 351 BOOLEAN Success = FALSE; 352 PCWSTR PathName; 353 USHORT i; 354 USHORT LocalMachine; 355 UNICODE_STRING LocalVendorName; 356 WCHAR VendorNameBuffer[MAX_PATH]; 357 358 /* Check for VendorName validity */ 359 if (VendorName->MaximumLength < sizeof(UNICODE_NULL)) 360 { 361 /* Don't use it, invalidate the pointer */ 362 VendorName = NULL; 363 } 364 else 365 { 366 /* Zero it out */ 367 *VendorName->Buffer = UNICODE_NULL; 368 VendorName->Length = 0; 369 } 370 371 /* Check for the existence of \SystemRoot\System32 */ 372 PathName = L"System32\\"; 373 if (!DoesDirExist(SystemRootDirectory, PathName)) 374 { 375 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 376 return FALSE; 377 } 378 379 /* Check for the existence of \SystemRoot\System32\drivers */ 380 PathName = L"System32\\drivers\\"; 381 if (!DoesDirExist(SystemRootDirectory, PathName)) 382 { 383 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 384 return FALSE; 385 } 386 387 /* Check for the existence of \SystemRoot\System32\config */ 388 PathName = L"System32\\config\\"; 389 if (!DoesDirExist(SystemRootDirectory, PathName)) 390 { 391 // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status); 392 return FALSE; 393 } 394 395 #if 0 396 /* 397 * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config 398 * (but we don't check here whether they are actually valid). 399 */ 400 PathName = L"System32\\config\\SYSTEM"; 401 if (!DoesFileExist(SystemRootDirectory, PathName)) 402 { 403 // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status); 404 return FALSE; 405 } 406 PathName = L"System32\\config\\SOFTWARE"; 407 if (!DoesFileExist(SystemRootDirectory, PathName)) 408 { 409 // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status); 410 return FALSE; 411 } 412 #endif 413 414 RtlInitEmptyUnicodeString(&LocalVendorName, VendorNameBuffer, sizeof(VendorNameBuffer)); 415 416 /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */ 417 PathName = L"System32\\ntoskrnl.exe"; 418 Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName); 419 if (!Success) 420 DPRINT1("Kernel executable '%S' is either not a PE file, or does not have any vendor?\n", PathName); 421 422 /* 423 * The kernel gives the OS its flavour. If we failed due to the absence of 424 * ntoskrnl.exe this might be due to the fact this particular installation 425 * uses a custom kernel that has a different name, overridden in the boot 426 * parameters. We then rely on the existence of ntdll.dll, which cannot be 427 * renamed on a valid NT system. 428 */ 429 if (Success) 430 { 431 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) 432 { 433 Success = !!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]); 434 if (Success) 435 { 436 /* We have found a correct vendor combination */ 437 DPRINT("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]); 438 break; 439 } 440 } 441 442 /* Return the target architecture */ 443 if (Machine) 444 { 445 /* Copy the value and invalidate the pointer */ 446 *Machine = LocalMachine; 447 Machine = NULL; 448 } 449 450 /* Return the vendor name */ 451 if (VendorName) 452 { 453 /* Copy the string and invalidate the pointer */ 454 RtlCopyUnicodeString(VendorName, &LocalVendorName); 455 VendorName = NULL; 456 } 457 } 458 459 /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */ 460 461 /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */ 462 PathName = L"System32\\ntdll.dll"; 463 Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName); 464 if (!Success) 465 DPRINT1("User-mode DLL '%S' is either not a PE file, or does not have any vendor?\n", PathName); 466 467 if (Success) 468 { 469 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) 470 { 471 if (!!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i])) 472 { 473 /* We have found a correct vendor combination */ 474 DPRINT("IsValidNTOSInstallation: The user-mode DLL '%S' is from %S\n", PathName, KnownVendors[i]); 475 break; 476 } 477 } 478 479 /* Return the target architecture if not already obtained */ 480 if (Machine) 481 { 482 /* Copy the value and invalidate the pointer */ 483 *Machine = LocalMachine; 484 Machine = NULL; 485 } 486 487 /* Return the vendor name if not already obtained */ 488 if (VendorName) 489 { 490 /* Copy the string and invalidate the pointer */ 491 RtlCopyUnicodeString(VendorName, &LocalVendorName); 492 VendorName = NULL; 493 } 494 } 495 496 return Success; 497 } 498 499 static BOOLEAN 500 IsValidNTOSInstallation( 501 IN PUNICODE_STRING SystemRootPath, 502 OUT PUSHORT Machine, 503 OUT PUNICODE_STRING VendorName OPTIONAL) 504 { 505 NTSTATUS Status; 506 OBJECT_ATTRIBUTES ObjectAttributes; 507 IO_STATUS_BLOCK IoStatusBlock; 508 HANDLE SystemRootDirectory; 509 BOOLEAN Success; 510 511 /* Open SystemRootPath */ 512 InitializeObjectAttributes(&ObjectAttributes, 513 SystemRootPath, 514 OBJ_CASE_INSENSITIVE, 515 NULL, 516 NULL); 517 Status = NtOpenFile(&SystemRootDirectory, 518 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, 519 &ObjectAttributes, 520 &IoStatusBlock, 521 FILE_SHARE_READ | FILE_SHARE_WRITE, 522 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); 523 if (!NT_SUCCESS(Status)) 524 { 525 DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", SystemRootPath, Status); 526 return FALSE; 527 } 528 529 Success = IsValidNTOSInstallationByHandle(SystemRootDirectory, 530 Machine, VendorName); 531 532 /* Done! */ 533 NtClose(SystemRootDirectory); 534 return Success; 535 } 536 537 #ifndef NDEBUG 538 static VOID 539 DumpNTOSInstalls( 540 IN PGENERIC_LIST List) 541 { 542 PGENERIC_LIST_ENTRY Entry; 543 PNTOS_INSTALLATION NtOsInstall; 544 ULONG NtOsInstallsCount = GetNumberOfListEntries(List); 545 546 DPRINT("There %s %d installation%s detected:\n", 547 NtOsInstallsCount >= 2 ? "are" : "is", 548 NtOsInstallsCount, 549 NtOsInstallsCount >= 2 ? "s" : ""); 550 551 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry)) 552 { 553 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry); 554 555 DPRINT(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n", 556 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, 557 NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath); 558 } 559 560 DPRINT("Done.\n"); 561 } 562 #endif 563 564 static PNTOS_INSTALLATION 565 FindExistingNTOSInstall( 566 IN PGENERIC_LIST List, 567 IN PCWSTR SystemRootArcPath OPTIONAL, 568 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ? 569 ) 570 { 571 PGENERIC_LIST_ENTRY Entry; 572 PNTOS_INSTALLATION NtOsInstall; 573 UNICODE_STRING SystemArcPath; 574 575 /* 576 * We search either via ARC path or NT path. 577 * If both pointers are NULL then we fail straight away. 578 */ 579 if (!SystemRootArcPath && !SystemRootNtPath) 580 return NULL; 581 582 RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath); 583 584 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry)) 585 { 586 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry); 587 588 /* 589 * Note that if both ARC paths are equal, then the corresponding 590 * NT paths must be the same. However, two ARC paths may be different 591 * but resolve into the same NT path. 592 */ 593 if ( (SystemRootArcPath && 594 RtlEqualUnicodeString(&NtOsInstall->SystemArcPath, 595 &SystemArcPath, TRUE)) || 596 (SystemRootNtPath && 597 RtlEqualUnicodeString(&NtOsInstall->SystemNtPath, 598 SystemRootNtPath, TRUE)) ) 599 { 600 /* Found it! */ 601 return NtOsInstall; 602 } 603 } 604 605 return NULL; 606 } 607 608 static PNTOS_INSTALLATION 609 AddNTOSInstallation( 610 _In_ PGENERIC_LIST List, 611 _In_ PCWSTR InstallationName, 612 _In_ USHORT Machine, 613 _In_ PCWSTR VendorName, 614 _In_ PCWSTR SystemRootArcPath, 615 _In_ PUNICODE_STRING SystemRootNtPath, // or PCWSTR ? 616 _In_ PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer 617 _In_ ULONG DiskNumber, 618 _In_ ULONG PartitionNumber) 619 { 620 PNTOS_INSTALLATION NtOsInstall; 621 SIZE_T ArcPathLength, NtPathLength; 622 623 /* Is there already any installation with these settings? */ 624 NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath); 625 if (NtOsInstall) 626 { 627 DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n", 628 NtOsInstall->InstallationName, NtOsInstall->VendorName, 629 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath); 630 // 631 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE... 632 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file. 633 // 634 return NtOsInstall; 635 } 636 637 ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR); 638 // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR)); 639 NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL); 640 641 /* None was found, so add a new one */ 642 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, 643 sizeof(*NtOsInstall) + 644 ArcPathLength + NtPathLength); 645 if (!NtOsInstall) 646 return NULL; 647 648 NtOsInstall->DiskNumber = DiskNumber; 649 NtOsInstall->PartitionNumber = PartitionNumber; 650 NtOsInstall->Machine = Machine; 651 652 RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath, 653 (PWCHAR)(NtOsInstall + 1), 654 ArcPathLength); 655 RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength); 656 NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL); 657 658 RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath, 659 (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength), 660 NtPathLength); 661 RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath); 662 NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer + 663 (PathComponent - SystemRootNtPath->Buffer); 664 665 RtlStringCchCopyW(NtOsInstall->InstallationName, 666 ARRAYSIZE(NtOsInstall->InstallationName), 667 InstallationName); 668 669 RtlStringCchCopyW(NtOsInstall->VendorName, 670 ARRAYSIZE(NtOsInstall->VendorName), 671 VendorName); 672 673 AppendGenericListEntry(List, NtOsInstall, FALSE); 674 675 return NtOsInstall; 676 } 677 678 static VOID 679 FindNTOSInstallations( 680 _Inout_ PGENERIC_LIST List, 681 _In_ PPARTLIST PartList, 682 _In_ PVOLENTRY Volume) 683 { 684 NTSTATUS Status; 685 HANDLE VolumeRootDirHandle; 686 OBJECT_ATTRIBUTES ObjectAttributes; 687 IO_STATUS_BLOCK IoStatusBlock; 688 UNICODE_STRING VolumeRootPath; 689 BOOT_STORE_TYPE Type; 690 PVOID BootStoreHandle; 691 ENUM_INSTALLS_DATA Data; 692 ULONG Version; 693 WCHAR PathBuffer[RTL_NUMBER_OF_FIELD(VOLINFO, DeviceName) + 1]; 694 695 /* Set VolumeRootPath */ 696 RtlStringCchPrintfW(PathBuffer, _countof(PathBuffer), 697 L"%s\\", Volume->Info.DeviceName); 698 RtlInitUnicodeString(&VolumeRootPath, PathBuffer); 699 DPRINT("FindNTOSInstallations(%wZ)\n", &VolumeRootPath); 700 701 /* Open the volume */ 702 InitializeObjectAttributes(&ObjectAttributes, 703 &VolumeRootPath, 704 OBJ_CASE_INSENSITIVE, 705 NULL, 706 NULL); 707 Status = NtOpenFile(&VolumeRootDirHandle, 708 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, 709 &ObjectAttributes, 710 &IoStatusBlock, 711 FILE_SHARE_READ | FILE_SHARE_WRITE, 712 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); 713 if (!NT_SUCCESS(Status)) 714 { 715 DPRINT1("Failed to open volume '%wZ', Status 0x%08lx\n", &VolumeRootPath, Status); 716 return; 717 } 718 719 Data.List = List; 720 Data.PartList = PartList; 721 722 /* Try to see whether we recognize some NT boot loaders */ 723 for (Type = FreeLdr; Type < BldrTypeMax; ++Type) 724 { 725 Status = FindBootStore(VolumeRootDirHandle, Type, &Version); 726 if (!NT_SUCCESS(Status)) 727 { 728 /* The loader does not exist, continue with another one */ 729 DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n", 730 Type, Status); 731 continue; 732 } 733 734 /* The loader exists, try to enumerate its boot entries */ 735 DPRINT("Analyze the OS installations for loader type '%d' in Volume %wZ (Disk #%d, Partition #%d)\n", 736 Type, &VolumeRootPath, 737 Volume->PartEntry->DiskEntry->DiskNumber, 738 Volume->PartEntry->PartitionNumber); 739 740 Status = OpenBootStoreByHandle(&BootStoreHandle, VolumeRootDirHandle, Type, 741 BS_OpenExisting, BS_ReadAccess); 742 if (!NT_SUCCESS(Status)) 743 { 744 DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n", 745 Type, Status); 746 continue; 747 } 748 EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data); 749 CloseBootStore(BootStoreHandle); 750 } 751 752 /* Close the volume */ 753 NtClose(VolumeRootDirHandle); 754 } 755 756 /** 757 * @brief 758 * Create a list of available NT OS installations on the computer, 759 * by searching for recognized ones on each recognized storage volume. 760 **/ 761 // EnumerateNTOSInstallations 762 PGENERIC_LIST 763 CreateNTOSInstallationsList( 764 _In_ PPARTLIST PartList) 765 { 766 PGENERIC_LIST List; 767 PLIST_ENTRY Entry; 768 PVOLENTRY Volume; 769 BOOLEAN CheckVolume; 770 771 List = CreateGenericList(); 772 if (!List) 773 return NULL; 774 775 /* Loop each available volume */ 776 for (Entry = PartList->VolumesList.Flink; 777 Entry != &PartList->VolumesList; 778 Entry = Entry->Flink) 779 { 780 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 781 /* Valid OS installations can be found only on basic volumes */ 782 if (!Volume->PartEntry) // TODO: In the future: (!Volume->IsSimpleVolume) 783 continue; 784 785 CheckVolume = (!Volume->New && (Volume->FormatState == Formatted)); 786 787 #ifndef NDEBUG 788 { 789 PPARTENTRY PartEntry = Volume->PartEntry; 790 ASSERT(PartEntry->Volume == Volume); 791 DPRINT("Volume %S (%c%c) on Disk #%d, Partition #%d (%s), " 792 "index %d - Type 0x%02x, IsVolNew = %s, FormatState = %lu -- Should I check it? %s\n", 793 Volume->Info.DeviceName, 794 !Volume->Info.DriveLetter ? '-' : (CHAR)Volume->Info.DriveLetter, 795 !Volume->Info.DriveLetter ? '-' : ':', 796 PartEntry->DiskEntry->DiskNumber, 797 PartEntry->PartitionNumber, 798 PartEntry->LogicalPartition ? "Logical" : "Primary", 799 PartEntry->PartitionIndex, 800 PartEntry->PartitionType, 801 Volume->New ? "Yes" : "No", 802 Volume->FormatState, 803 CheckVolume ? "YES!" : "NO!"); 804 } 805 #endif 806 807 if (CheckVolume) 808 FindNTOSInstallations(List, PartList, Volume); 809 } 810 811 #ifndef NDEBUG 812 /**** Debugging: List all the collected installations ****/ 813 DumpNTOSInstalls(List); 814 #endif 815 816 return List; 817 } 818 819 /* EOF */ 820