1 /* 2 * PROJECT: ReactOS Setup Library 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: NT 5.x family (MS Windows <= 2003, and ReactOS) 5 * operating systems detection code. 6 * COPYRIGHT: Copyright 2017-2018 Hermes Belusca-Maito 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 IN PPARTENTRY PartEntry OPTIONAL); 59 60 typedef struct _ENUM_INSTALLS_DATA 61 { 62 IN OUT PGENERIC_LIST List; 63 IN PPARTLIST PartList; 64 // IN PPARTENTRY PartEntry; 65 } ENUM_INSTALLS_DATA, *PENUM_INSTALLS_DATA; 66 67 // PENUM_BOOT_ENTRIES_ROUTINE 68 static NTSTATUS 69 NTAPI 70 EnumerateInstallations( 71 IN BOOT_STORE_TYPE Type, 72 IN PBOOT_STORE_ENTRY BootEntry, 73 IN PVOID Parameter OPTIONAL) 74 { 75 PENUM_INSTALLS_DATA Data = (PENUM_INSTALLS_DATA)Parameter; 76 PNTOS_OPTIONS Options = (PNTOS_OPTIONS)&BootEntry->OsOptions; 77 PNTOS_INSTALLATION NtOsInstall; 78 79 ULONG DiskNumber = 0, PartitionNumber = 0; 80 PCWSTR PathComponent = NULL; 81 PDISKENTRY DiskEntry = NULL; 82 PPARTENTRY PartEntry = NULL; 83 84 UNICODE_STRING SystemRootPath; 85 WCHAR SystemRoot[MAX_PATH]; 86 87 USHORT Machine; 88 UNICODE_STRING VendorName; 89 WCHAR VendorNameBuffer[MAX_PATH]; 90 91 92 /* We have a boot entry */ 93 94 /* Check for supported boot type "Windows2003" */ 95 if (BootEntry->OsOptionsLength < sizeof(NTOS_OPTIONS) || 96 RtlCompareMemory(&BootEntry->OsOptions /* Signature */, 97 NTOS_OPTIONS_SIGNATURE, 98 RTL_FIELD_SIZE(NTOS_OPTIONS, Signature)) != 99 RTL_FIELD_SIZE(NTOS_OPTIONS, Signature)) 100 { 101 /* This is not a ReactOS entry */ 102 // DPRINT(" An installation '%S' of unsupported type '%S'\n", 103 // BootEntry->FriendlyName, BootEntry->Version ? BootEntry->Version : L"n/a"); 104 DPRINT(" An installation '%S' of unsupported type %lu\n", 105 BootEntry->FriendlyName, BootEntry->OsOptionsLength); 106 /* Continue the enumeration */ 107 return STATUS_SUCCESS; 108 } 109 110 /* BootType is Windows2003, now check OsLoadPath */ 111 if (!Options->OsLoadPath || !*Options->OsLoadPath) 112 { 113 /* Certainly not a ReactOS installation */ 114 DPRINT1(" A Win2k3 install '%S' without an ARC path?!\n", BootEntry->FriendlyName); 115 /* Continue the enumeration */ 116 return STATUS_SUCCESS; 117 } 118 119 DPRINT(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n", 120 BootEntry->FriendlyName, Options->OsLoadPath); 121 // DPRINT(" Found a Win2k3 install '%S' with ARC path '%S'\n", 122 // BootEntry->FriendlyName, Options->OsLoadPath); 123 124 // TODO: Normalize the ARC path. 125 126 /* 127 * Check whether we already have an installation with this ARC path. 128 * If this is the case, stop there. 129 */ 130 NtOsInstall = FindExistingNTOSInstall(Data->List, Options->OsLoadPath, NULL); 131 if (NtOsInstall) 132 { 133 DPRINT(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n", 134 NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemArcPath); 135 /* Continue the enumeration */ 136 return STATUS_SUCCESS; 137 } 138 139 /* 140 * Convert the ARC path into an NT path, from which we will deduce 141 * the real disk drive & partition on which the candidate installation 142 * resides, as well verifying whether it is indeed an NTOS installation. 143 */ 144 RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot)); 145 if (!ArcPathToNtPath(&SystemRootPath, Options->OsLoadPath, Data->PartList)) 146 { 147 DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", Options->OsLoadPath); 148 /* Continue the enumeration */ 149 return STATUS_SUCCESS; 150 } 151 152 DPRINT("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n", 153 Options->OsLoadPath, &SystemRootPath); 154 155 /* 156 * Check whether we already have an installation with this NT path. 157 * If this is the case, stop there. 158 */ 159 NtOsInstall = FindExistingNTOSInstall(Data->List, NULL /*Options->OsLoadPath*/, &SystemRootPath); 160 if (NtOsInstall) 161 { 162 DPRINT1(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n", 163 NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemNtPath); 164 /* Continue the enumeration */ 165 return STATUS_SUCCESS; 166 } 167 168 DPRINT("EnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath); 169 170 /* Check if this is a valid NTOS installation; stop there if it isn't one */ 171 RtlInitEmptyUnicodeString(&VendorName, VendorNameBuffer, sizeof(VendorNameBuffer)); 172 if (!IsValidNTOSInstallation(&SystemRootPath, &Machine, &VendorName)) 173 { 174 /* Continue the enumeration */ 175 return STATUS_SUCCESS; 176 } 177 178 DPRINT("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", 179 Options->OsLoadPath, &SystemRootPath); 180 181 /* From the NT path, compute the disk, partition and path components */ 182 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent)) 183 { 184 DPRINT("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n", 185 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent); 186 187 /* Retrieve the corresponding disk and partition */ 188 if (!GetDiskOrPartition(Data->PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry)) 189 { 190 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", 191 DiskNumber, PartitionNumber); 192 } 193 } 194 else 195 { 196 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath); 197 } 198 199 /* Add the discovered NTOS installation into the list */ 200 AddNTOSInstallation(Data->List, 201 BootEntry->FriendlyName, 202 Machine, 203 VendorName.Buffer, // FIXME: What if it's not NULL-terminated? 204 Options->OsLoadPath, 205 &SystemRootPath, PathComponent, 206 DiskNumber, PartitionNumber, PartEntry); 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 IN PPARTENTRY PartEntry OPTIONAL) 620 { 621 PNTOS_INSTALLATION NtOsInstall; 622 SIZE_T ArcPathLength, NtPathLength; 623 624 /* Is there already any installation with these settings? */ 625 NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath); 626 if (NtOsInstall) 627 { 628 DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n", 629 NtOsInstall->InstallationName, NtOsInstall->VendorName, 630 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath); 631 // 632 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE... 633 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file. 634 // 635 return NtOsInstall; 636 } 637 638 ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR); 639 // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR)); 640 NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL); 641 642 /* None was found, so add a new one */ 643 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, 644 sizeof(*NtOsInstall) + 645 ArcPathLength + NtPathLength); 646 if (!NtOsInstall) 647 return NULL; 648 649 NtOsInstall->DiskNumber = DiskNumber; 650 NtOsInstall->PartitionNumber = PartitionNumber; 651 NtOsInstall->PartEntry = PartEntry; 652 653 NtOsInstall->Machine = Machine; 654 655 RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath, 656 (PWCHAR)(NtOsInstall + 1), 657 ArcPathLength); 658 RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength); 659 NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL); 660 661 RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath, 662 (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength), 663 NtPathLength); 664 RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath); 665 NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer + 666 (PathComponent - SystemRootNtPath->Buffer); 667 668 RtlStringCchCopyW(NtOsInstall->InstallationName, 669 ARRAYSIZE(NtOsInstall->InstallationName), 670 InstallationName); 671 672 RtlStringCchCopyW(NtOsInstall->VendorName, 673 ARRAYSIZE(NtOsInstall->VendorName), 674 VendorName); 675 676 AppendGenericListEntry(List, NtOsInstall, FALSE); 677 678 return NtOsInstall; 679 } 680 681 static VOID 682 FindNTOSInstallations( 683 IN OUT PGENERIC_LIST List, 684 IN PPARTLIST PartList, 685 IN PPARTENTRY PartEntry) 686 { 687 NTSTATUS Status; 688 ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber; 689 ULONG PartitionNumber = PartEntry->PartitionNumber; 690 HANDLE PartitionDirectoryHandle; 691 OBJECT_ATTRIBUTES ObjectAttributes; 692 IO_STATUS_BLOCK IoStatusBlock; 693 UNICODE_STRING PartitionRootPath; 694 BOOT_STORE_TYPE Type; 695 PVOID BootStoreHandle; 696 ENUM_INSTALLS_DATA Data; 697 ULONG Version; 698 WCHAR PathBuffer[MAX_PATH]; 699 700 ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0); 701 702 /* Set PartitionRootPath */ 703 RtlStringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), 704 L"\\Device\\Harddisk%lu\\Partition%lu\\", 705 DiskNumber, PartitionNumber); 706 RtlInitUnicodeString(&PartitionRootPath, PathBuffer); 707 DPRINT("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath); 708 709 /* Open the partition */ 710 InitializeObjectAttributes(&ObjectAttributes, 711 &PartitionRootPath, 712 OBJ_CASE_INSENSITIVE, 713 NULL, 714 NULL); 715 Status = NtOpenFile(&PartitionDirectoryHandle, 716 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, 717 &ObjectAttributes, 718 &IoStatusBlock, 719 FILE_SHARE_READ | FILE_SHARE_WRITE, 720 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); 721 if (!NT_SUCCESS(Status)) 722 { 723 DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status); 724 return; 725 } 726 727 Data.List = List; 728 Data.PartList = PartList; 729 730 /* Try to see whether we recognize some NT boot loaders */ 731 for (Type = FreeLdr; Type < BldrTypeMax; ++Type) 732 { 733 Status = FindBootStore(PartitionDirectoryHandle, Type, &Version); 734 if (!NT_SUCCESS(Status)) 735 { 736 /* The loader does not exist, continue with another one */ 737 DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n", 738 Type, Status); 739 continue; 740 } 741 742 /* The loader exists, try to enumerate its boot entries */ 743 DPRINT("Analyze the OS installations for loader type '%d' in disk #%d, partition #%d\n", 744 Type, DiskNumber, PartitionNumber); 745 746 Status = OpenBootStoreByHandle(&BootStoreHandle, PartitionDirectoryHandle, Type, 747 BS_OpenExisting, BS_ReadAccess); 748 if (!NT_SUCCESS(Status)) 749 { 750 DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n", 751 Type, Status); 752 continue; 753 } 754 EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data); 755 CloseBootStore(BootStoreHandle); 756 } 757 758 /* Close the partition */ 759 NtClose(PartitionDirectoryHandle); 760 } 761 762 // static 763 FORCEINLINE BOOLEAN 764 ShouldICheckThisPartition( 765 IN PPARTENTRY PartEntry) 766 { 767 if (!PartEntry) 768 return FALSE; 769 770 return PartEntry->IsPartitioned && 771 !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ && 772 !PartEntry->New && 773 (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */); 774 } 775 776 // EnumerateNTOSInstallations 777 PGENERIC_LIST 778 CreateNTOSInstallationsList( 779 IN PPARTLIST PartList) 780 { 781 PGENERIC_LIST List; 782 PLIST_ENTRY Entry, Entry2; 783 PDISKENTRY DiskEntry; 784 PPARTENTRY PartEntry; 785 786 List = CreateGenericList(); 787 if (List == NULL) 788 return NULL; 789 790 /* Loop each available disk ... */ 791 Entry = PartList->DiskListHead.Flink; 792 while (Entry != &PartList->DiskListHead) 793 { 794 DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry); 795 Entry = Entry->Flink; 796 797 DPRINT("Disk #%d\n", DiskEntry->DiskNumber); 798 799 /* ... and for each disk, loop each available partition */ 800 801 /* First, the primary partitions */ 802 Entry2 = DiskEntry->PrimaryPartListHead.Flink; 803 while (Entry2 != &DiskEntry->PrimaryPartListHead) 804 { 805 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry); 806 Entry2 = Entry2->Flink; 807 808 ASSERT(PartEntry->DiskEntry == DiskEntry); 809 810 DPRINT(" Primary Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, FormatState = %lu -- Should I check it? %s\n", 811 PartEntry->PartitionNumber, PartEntry->PartitionIndex, 812 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE", 813 PartEntry->IsPartitioned ? "TRUE" : "FALSE", 814 PartEntry->New ? "Yes" : "No", 815 PartEntry->FormatState, 816 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!"); 817 818 if (ShouldICheckThisPartition(PartEntry)) 819 FindNTOSInstallations(List, PartList, PartEntry); 820 } 821 822 /* Then, the logical partitions (present in the extended partition) */ 823 Entry2 = DiskEntry->LogicalPartListHead.Flink; 824 while (Entry2 != &DiskEntry->LogicalPartListHead) 825 { 826 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry); 827 Entry2 = Entry2->Flink; 828 829 ASSERT(PartEntry->DiskEntry == DiskEntry); 830 831 DPRINT(" Logical Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, FormatState = %lu -- Should I check it? %s\n", 832 PartEntry->PartitionNumber, PartEntry->PartitionIndex, 833 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE", 834 PartEntry->IsPartitioned ? "TRUE" : "FALSE", 835 PartEntry->New ? "Yes" : "No", 836 PartEntry->FormatState, 837 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!"); 838 839 if (ShouldICheckThisPartition(PartEntry)) 840 FindNTOSInstallations(List, PartList, PartEntry); 841 } 842 } 843 844 #ifndef NDEBUG 845 /**** Debugging: List all the collected installations ****/ 846 DumpNTOSInstalls(List); 847 #endif 848 849 return List; 850 } 851 852 /* EOF */ 853