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
18 /* GLOBALS ******************************************************************/
20 extern PPARTLIST PartitionList
;
22 /* Language-independent Vendor strings */
23 static const PCWSTR KnownVendors
[] = { L
"ReactOS", L
"Microsoft" };
26 /* FUNCTIONS ****************************************************************/
30 BOOL
IsWindowsOS(VOID
)
33 // Load the "SystemRoot\System32\Config\SOFTWARE" hive and mount it,
34 // then go to (SOFTWARE\\)Microsoft\\Windows NT\\CurrentVersion,
35 // check the REG_SZ value "ProductName" and see whether it's "Windows"
36 // or "ReactOS". One may also check the REG_SZ "CurrentVersion" value,
37 // the REG_SZ "SystemRoot" and "PathName" values (what are the differences??).
39 // Optionally, looking at the SYSTEM hive, CurrentControlSet\\Control,
40 // REG_SZ values "SystemBootDevice" (and "FirmwareBootDevice" ??)...
43 /* ReactOS reports as Windows NT 5.2 */
46 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
47 L
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
48 0, KEY_QUERY_VALUE
, &hKey
) == ERROR_SUCCESS
)
51 DWORD dwType
= 0, dwBufSize
= 0;
53 ret
= RegQueryValueExW(hKey
, L
"ProductName", NULL
, &dwType
, NULL
, &dwBufSize
);
54 if (ret
== ERROR_SUCCESS
&& dwType
== REG_SZ
)
56 LPTSTR lpszProductName
= (LPTSTR
)MemAlloc(0, dwBufSize
);
57 RegQueryValueExW(hKey
, L
"ProductName", NULL
, &dwType
, (LPBYTE
)lpszProductName
, &dwBufSize
);
59 bIsWindowsOS
= (FindSubStrI(lpszProductName
, _T("Windows")) != NULL
);
61 MemFree(lpszProductName
);
72 typedef enum _NTOS_BOOT_LOADER_TYPE
74 FreeLdr
, // ReactOS' FreeLDR
75 NtLdr
, // Windows <= 2k3 NTLDR
76 // BootMgr, // Vista+ BCD-oriented BOOTMGR
77 } NTOS_BOOT_LOADER_TYPE
;
79 typedef struct _NTOS_BOOT_LOADER_FILES
81 NTOS_BOOT_LOADER_TYPE Type
;
82 PCWSTR LoaderExecutable
;
83 PCWSTR LoaderConfigurationFile
;
84 // EnumerateInstallations;
85 } NTOS_BOOT_LOADER_FILES
, *PNTOS_BOOT_LOADER_FILES
;
87 // Question 1: What if config file is optional?
88 // Question 2: What if many config files are possible?
89 NTOS_BOOT_LOADER_FILES NtosBootLoaders
[] =
91 {FreeLdr
, L
"freeldr.sys", L
"freeldr.ini"},
92 {NtLdr
, L
"ntldr" , L
"boot.ini"},
93 {NtLdr
, L
"setupldr" , L
"txtsetup.sif"},
94 // {BootMgr, L"bootmgr" , ???}
99 IsValidNTOSInstallation(
100 IN HANDLE SystemRootDirectory OPTIONAL
,
101 IN PCWSTR SystemRoot OPTIONAL
);
103 static PNTOS_INSTALLATION
105 IN PGENERIC_LIST List
,
107 IN ULONG PartitionNumber
,
108 IN PCWSTR SystemRoot
,
109 IN PCWSTR InstallationName
);
112 FreeLdrEnumerateInstallations(
113 IN OUT PGENERIC_LIST List
,
114 IN PPARTLIST PartList
,
115 // IN PPARTENTRY PartEntry,
121 PINICACHEITERATOR Iterator
;
122 PINICACHESECTION IniSection
, OsIniSection
;
123 PWCHAR SectionName
, KeyData
;
124 WCHAR InstallName
[MAX_PATH
];
126 /* Open an *existing* FreeLdr.ini configuration file */
127 Status
= IniCacheLoadFromMemory(&IniCache
, FileBuffer
, FileLength
, FALSE
);
128 if (!NT_SUCCESS(Status
))
131 /* Get the "Operating Systems" section */
132 IniSection
= IniCacheGetSection(IniCache
, L
"Operating Systems");
133 if (IniSection
== NULL
)
135 IniCacheDestroy(IniCache
);
136 return STATUS_UNSUCCESSFUL
;
139 /* Enumerate all the valid installations */
140 Iterator
= IniCacheFindFirstValue(IniSection
, &SectionName
, &KeyData
);
141 if (!Iterator
) goto Quit
;
144 // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni).
145 if (KeyData
[0] == L
'"')
147 /* Quoted name, copy up to the closing quote */
148 PWCHAR Begin
= &KeyData
[1];
149 PWCHAR End
= wcschr(Begin
, L
'"');
151 End
= Begin
+ wcslen(Begin
);
152 StringCchCopyNW(InstallName
, ARRAYSIZE(InstallName
),
157 /* Non-quoted name, copy everything */
158 StringCchCopyW(InstallName
, ARRAYSIZE(InstallName
), KeyData
);
161 DPRINT1("Possible installation '%S' in OS section '%S'\n", InstallName
, SectionName
);
163 /* Search for an existing ReactOS entry */
164 OsIniSection
= IniCacheGetSection(IniCache
, SectionName
);
168 /* Check for supported boot type "Windows2003" */
169 Status
= IniCacheGetKey(OsIniSection
, L
"BootType", &KeyData
);
170 if (NT_SUCCESS(Status
))
172 // TODO: What to do with "Windows" ; "WindowsNT40" ; "ReactOSSetup" ?
173 if ((KeyData
== NULL
) ||
174 ( (_wcsicmp(KeyData
, L
"Windows2003") != 0) &&
175 (_wcsicmp(KeyData
, L
"\"Windows2003\"") != 0) ))
177 /* This is not a ReactOS entry */
183 /* Certainly not a ReactOS installation */
187 /* BootType is Windows2003. Now check SystemPath. */
188 Status
= IniCacheGetKey(OsIniSection
, L
"SystemPath", &KeyData
);
189 if (!NT_SUCCESS(Status
))
191 DPRINT1(" A Win2k3 install '%S' without an ARC path?!\n", InstallName
);
196 HANDLE SystemRootDirectory
;
197 OBJECT_ATTRIBUTES ObjectAttributes
;
198 IO_STATUS_BLOCK IoStatusBlock
;
199 UNICODE_STRING SystemRootPath
;
200 WCHAR SystemRoot
[MAX_PATH
];
201 WCHAR InstallNameW
[MAX_PATH
];
203 DPRINT1(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n", InstallName
, KeyData
);
205 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
208 // TODO 1: Normalize the ARC path.
210 // TODO 2: Check whether we already have an installation with this ARC path.
211 // If that's the case, stop there. If not, continue...
214 * Convert the ARC path into an NT path, from which we will deduce
215 * the real disk drive & partition on which the candidate installation
216 * resides, as well verifying whether it is indeed an NTOS installation.
218 RtlInitEmptyUnicodeString(&SystemRootPath
, SystemRoot
, sizeof(SystemRoot
));
219 if (!ArcPathToNtPath(&SystemRootPath
, KeyData
, PartList
))
221 DPRINT1("ArcPathToNtPath(%S) failed, installation skipped.\n", KeyData
);
222 // FIXME: Do not continue!
226 DPRINT1("ArcPathToNtPath() succeeded: %S --> %wZ\n", KeyData
, &SystemRootPath
);
228 // TODO 3: Check whether we already have an installation with this NT path.
229 // If that's the case, stop there. If not, continue...
231 /* Set SystemRootPath */
232 DPRINT1("FreeLdrEnumerateInstallations: SystemRootPath: %wZ\n", &SystemRootPath
);
234 /* Open SystemRootPath */
235 InitializeObjectAttributes(&ObjectAttributes
,
237 OBJ_CASE_INSENSITIVE
,
240 Status
= NtOpenFile(&SystemRootDirectory
,
241 FILE_LIST_DIRECTORY
| SYNCHRONIZE
,
244 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
245 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_DIRECTORY_FILE
);
246 if (!NT_SUCCESS(Status
))
248 DPRINT1("Failed to open SystemRoot %wZ, Status 0x%08lx\n", &SystemRootPath
, Status
);
252 if (IsValidNTOSInstallation(SystemRootDirectory
, NULL
))
254 ULONG DiskNumber
, PartitionNumber
;
255 PCWSTR PathComponent
;
256 PDISKENTRY DiskEntry
= NULL
;
257 PPARTENTRY PartEntry
= NULL
;
259 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path %S, NT path %wZ\n", KeyData
, &SystemRootPath
);
261 /* From the NT path, compute the disk, partition and path components */
262 if (NtPathToDiskPartComponents(SystemRootPath
.Buffer
, &DiskNumber
, &PartitionNumber
, &PathComponent
))
264 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
265 &SystemRootPath
, DiskNumber
, PartitionNumber
, PathComponent
);
267 /* Retrieve the corresponding disk and partition */
268 if (!GetDiskOrPartition(PartList
, DiskNumber
, PartitionNumber
, &DiskEntry
, &PartEntry
))
269 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber
, PartitionNumber
);
273 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath
);
276 if (PartEntry
&& PartEntry
->DriveLetter
)
278 /* We have retrieved a partition that is mounted */
279 StringCchPrintfW(InstallNameW
, ARRAYSIZE(InstallNameW
), L
"%C:%s \"%s\"",
280 PartEntry
->DriveLetter
, PathComponent
, InstallName
);
284 /* We failed somewhere, just show the NT path */
285 StringCchPrintfW(InstallNameW
, ARRAYSIZE(InstallNameW
), L
"%wZ \"%s\"",
286 &SystemRootPath
, InstallName
);
288 AddNTOSInstallation(List
, 0, 0 /*DiskNumber, PartitionNumber*/, KeyData
, InstallNameW
);
291 NtClose(SystemRootDirectory
);
294 while (IniCacheFindNextValue(Iterator
, &SectionName
, &KeyData
));
296 IniCacheFindClose(Iterator
);
299 IniCacheDestroy(IniCache
);
300 return STATUS_SUCCESS
;
304 NtLdrEnumerateInstallations(
305 IN OUT PGENERIC_LIST List
,
306 IN PPARTLIST PartList
,
307 // IN PPARTENTRY PartEntry,
313 PINICACHEITERATOR Iterator
;
314 PINICACHESECTION IniSection
;
315 PWCHAR SectionName
, KeyData
;
316 WCHAR InstallName
[MAX_PATH
];
318 /* Open an *existing* FreeLdr.ini configuration file */
319 Status
= IniCacheLoadFromMemory(&IniCache
, FileBuffer
, FileLength
, FALSE
);
320 if (!NT_SUCCESS(Status
))
323 /* Get the "Operating Systems" section */
324 IniSection
= IniCacheGetSection(IniCache
, L
"operating systems");
325 if (IniSection
== NULL
)
327 IniCacheDestroy(IniCache
);
328 return STATUS_UNSUCCESSFUL
;
331 /* Enumerate all the valid installations */
332 Iterator
= IniCacheFindFirstValue(IniSection
, &SectionName
, &KeyData
);
333 if (!Iterator
) goto Quit
;
336 // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni).
337 if (KeyData
[0] == L
'"')
339 /* Quoted name, copy up to the closing quote */
340 PWCHAR Begin
= &KeyData
[1];
341 PWCHAR End
= wcschr(Begin
, L
'"');
343 End
= Begin
+ wcslen(Begin
);
344 StringCchCopyNW(InstallName
, ARRAYSIZE(InstallName
),
349 /* Non-quoted name, copy everything */
350 StringCchCopyW(InstallName
, ARRAYSIZE(InstallName
), KeyData
);
353 DPRINT1("Possible installation '%S' with ARC path '%S'\n", InstallName
, SectionName
);
355 // FIXME TODO: Determine whether we indeed have an ARC path, in which case
356 // this is an NT installation, or, whether we have something else like a DOS
357 // path, which means that we are booting a boot sector...
359 DPRINT1(" Found a Win2k3 install '%S' with ARC path '%S'\n", InstallName
, SectionName
);
360 // TODO: Dissect it in order to retrieve the real disk drive & partition numbers.
361 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
364 HANDLE SystemRootDirectory
;
365 OBJECT_ATTRIBUTES ObjectAttributes
;
366 IO_STATUS_BLOCK IoStatusBlock
;
367 UNICODE_STRING SystemRootPath
;
368 WCHAR SystemRoot
[MAX_PATH
];
369 WCHAR InstallNameW
[MAX_PATH
];
371 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
374 // TODO 1: Normalize the ARC path.
376 // TODO 2: Check whether we already have an installation with this ARC path.
377 // If that's the case, stop there. If not, continue...
380 * Convert the ARC path into an NT path, from which we will deduce
381 * the real disk drive & partition on which the candidate installation
382 * resides, as well verifying whether it is indeed an NTOS installation.
384 RtlInitEmptyUnicodeString(&SystemRootPath
, SystemRoot
, sizeof(SystemRoot
));
385 if (!ArcPathToNtPath(&SystemRootPath
, SectionName
, PartList
))
387 DPRINT1("ArcPathToNtPath(%S) failed, installation skipped.\n", SectionName
);
388 // FIXME: Do not continue!
392 DPRINT1("ArcPathToNtPath() succeeded: %S --> %wZ\n", SectionName
, &SystemRootPath
);
394 // TODO 3: Check whether we already have an installation with this NT path.
395 // If that's the case, stop there. If not, continue...
397 /* Set SystemRootPath */
398 DPRINT1("NtLdrEnumerateInstallations: SystemRootPath: %wZ\n", &SystemRootPath
);
400 /* Open SystemRootPath */
401 InitializeObjectAttributes(&ObjectAttributes
,
403 OBJ_CASE_INSENSITIVE
,
406 Status
= NtOpenFile(&SystemRootDirectory
,
407 FILE_LIST_DIRECTORY
| SYNCHRONIZE
,
410 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
411 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_DIRECTORY_FILE
);
412 if (!NT_SUCCESS(Status
))
414 DPRINT1("Failed to open SystemRoot %wZ, Status 0x%08lx\n", &SystemRootPath
, Status
);
418 if (IsValidNTOSInstallation(SystemRootDirectory
, NULL
))
420 ULONG DiskNumber
, PartitionNumber
;
421 PCWSTR PathComponent
;
422 PDISKENTRY DiskEntry
= NULL
;
423 PPARTENTRY PartEntry
= NULL
;
425 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path %S, NT path %wZ\n", SectionName
, &SystemRootPath
);
427 /* From the NT path, compute the disk, partition and path components */
428 if (NtPathToDiskPartComponents(SystemRootPath
.Buffer
, &DiskNumber
, &PartitionNumber
, &PathComponent
))
430 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
431 &SystemRootPath
, DiskNumber
, PartitionNumber
, PathComponent
);
433 /* Retrieve the corresponding disk and partition */
434 if (!GetDiskOrPartition(PartList
, DiskNumber
, PartitionNumber
, &DiskEntry
, &PartEntry
))
435 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber
, PartitionNumber
);
439 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath
);
442 if (PartEntry
&& PartEntry
->DriveLetter
)
444 /* We have retrieved a partition that is mounted */
445 StringCchPrintfW(InstallNameW
, ARRAYSIZE(InstallNameW
), L
"%C:%s \"%s\"",
446 PartEntry
->DriveLetter
, PathComponent
, InstallName
);
450 /* We failed somewhere, just show the NT path */
451 StringCchPrintfW(InstallNameW
, ARRAYSIZE(InstallNameW
), L
"%wZ \"%s\"",
452 &SystemRootPath
, InstallName
);
454 AddNTOSInstallation(List
, 0, 0 /*DiskNumber, PartitionNumber*/, SectionName
, InstallNameW
);
457 NtClose(SystemRootDirectory
);
460 while (IniCacheFindNextValue(Iterator
, &SectionName
, &KeyData
));
462 IniCacheFindClose(Iterator
);
465 IniCacheDestroy(IniCache
);
466 return STATUS_SUCCESS
;
470 * FindSubStrI(PCWSTR str, PCWSTR strSearch) :
471 * Searches for a sub-string 'strSearch' inside 'str', similarly to what
472 * wcsstr(str, strSearch) does, but ignores the case during the comparisons.
474 PWSTR
FindSubStrI(PCWSTR str
, PCWSTR strSearch
)
476 PWSTR cp
= (PWSTR
)str
;
485 s2
= (PWSTR
)strSearch
;
487 while (*s1
&& *s2
&& (towupper(*s1
) == towupper(*s2
)))
500 CheckForValidPEAndVendor(
501 IN HANDLE RootDirectory OPTIONAL
,
502 IN PCWSTR PathName OPTIONAL
,
503 IN PCWSTR FileName
, // OPTIONAL
504 OUT PUNICODE_STRING VendorName
507 BOOLEAN Success
= FALSE
;
509 HANDLE FileHandle
, SectionHandle
;
512 PVOID VersionBuffer
= NULL
; // Read-only
516 if (VendorName
->MaximumLength
< sizeof(UNICODE_NULL
))
519 *VendorName
->Buffer
= UNICODE_NULL
;
520 VendorName
->Length
= 0;
522 Status
= OpenAndMapFile(RootDirectory
, PathName
, FileName
,
523 &FileHandle
, &SectionHandle
, &ViewBase
, NULL
);
524 if (!NT_SUCCESS(Status
))
526 DPRINT1("Failed to open and map file %S, Status 0x%08lx\n", FileName
, Status
);
527 return FALSE
; // Status;
530 /* Make sure it's a valid PE file */
531 if (!RtlImageNtHeader(ViewBase
))
533 DPRINT1("File %S does not seem to be a valid PE, bail out\n", FileName
);
534 Status
= STATUS_INVALID_IMAGE_FORMAT
;
539 * Search for a valid executable version and vendor.
540 * NOTE: The module is loaded as a data file, it should be marked as such.
542 Status
= NtGetVersionResource((PVOID
)((ULONG_PTR
)ViewBase
| 1), &VersionBuffer
, NULL
);
543 if (!NT_SUCCESS(Status
))
545 DPRINT1("Failed to get version resource for file %S, Status 0x%08lx\n", FileName
, Status
);
549 Status
= NtVerQueryValue(VersionBuffer
, L
"\\VarFileInfo\\Translation", &pvData
, &BufLen
);
550 if (NT_SUCCESS(Status
))
552 USHORT wCodePage
= 0, wLangID
= 0;
553 WCHAR FileInfo
[MAX_PATH
];
555 wCodePage
= LOWORD(*(ULONG
*)pvData
);
556 wLangID
= HIWORD(*(ULONG
*)pvData
);
558 StringCchPrintfW(FileInfo
, ARRAYSIZE(FileInfo
),
559 L
"StringFileInfo\\%04X%04X\\CompanyName",
562 Status
= NtVerQueryValue(VersionBuffer
, FileInfo
, &pvData
, &BufLen
);
564 /* Fixup the Status in case pvData is NULL */
565 if (NT_SUCCESS(Status
) && !pvData
)
566 Status
= STATUS_NOT_FOUND
;
568 if (NT_SUCCESS(Status
) /*&& pvData*/)
570 /* BufLen includes the NULL terminator count */
571 DPRINT1("Found version vendor: \"%S\" for file %S\n", pvData
, FileName
);
573 StringCbCopyNW(VendorName
->Buffer
, VendorName
->MaximumLength
,
574 pvData
, BufLen
* sizeof(WCHAR
));
575 VendorName
->Length
= wcslen(VendorName
->Buffer
) * sizeof(WCHAR
);
581 if (!NT_SUCCESS(Status
))
582 DPRINT1("No version vendor found for file %S\n", FileName
);
585 /* Finally, unmap and close the file */
586 UnMapFile(SectionHandle
, ViewBase
);
593 // TODO: Instead of returning TRUE/FALSE, it would be nice to return
594 // a flag indicating:
595 // - whether the installation is actually valid;
596 // - if it's broken or not (aka. needs for repair, or just upgrading).
599 IsValidNTOSInstallation(
600 IN HANDLE SystemRootDirectory OPTIONAL
,
601 IN PCWSTR SystemRoot OPTIONAL
)
603 BOOLEAN Success
= FALSE
;
605 WCHAR PathBuffer
[MAX_PATH
];
606 UNICODE_STRING VendorName
;
609 * Use either the 'SystemRootDirectory' handle or the 'SystemRoot' string,
610 * depending on what the user gave to us in entry.
612 if (SystemRootDirectory
)
614 // else SystemRootDirectory == NULL and SystemRoot is what it is.
616 /* If both the parameters are NULL we cannot do anything else more */
617 if (!SystemRootDirectory
&& !SystemRoot
)
620 // DoesPathExist(SystemRootDirectory, SystemRoot, L"System32\\"); etc...
622 /* Check for the existence of \SystemRoot\System32 */
623 StringCchPrintfW(PathBuffer
, ARRAYSIZE(PathBuffer
), L
"%s%s", SystemRoot
? SystemRoot
: L
"", L
"System32\\");
624 if (!DoesPathExist(SystemRootDirectory
, PathBuffer
))
626 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
630 /* Check for the existence of \SystemRoot\System32\drivers */
631 StringCchPrintfW(PathBuffer
, ARRAYSIZE(PathBuffer
), L
"%s%s", SystemRoot
? SystemRoot
: L
"", L
"System32\\drivers\\");
632 if (!DoesPathExist(SystemRootDirectory
, PathBuffer
))
634 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
638 /* Check for the existence of \SystemRoot\System32\config */
639 StringCchPrintfW(PathBuffer
, ARRAYSIZE(PathBuffer
), L
"%s%s", SystemRoot
? SystemRoot
: L
"", L
"System32\\config\\");
640 if (!DoesPathExist(SystemRootDirectory
, PathBuffer
))
642 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
648 * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config
649 * (but we don't check here whether they are actually valid).
651 if (!DoesFileExist(SystemRootDirectory
, SystemRoot
, L
"System32\\config\\SYSTEM"))
653 // DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &FileName, Status);
656 if (!DoesFileExist(SystemRootDirectory
, SystemRoot
, L
"System32\\config\\SOFTWARE"))
658 // DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &FileName, Status);
663 RtlInitEmptyUnicodeString(&VendorName
, PathBuffer
, sizeof(PathBuffer
));
665 /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */
666 Success
= CheckForValidPEAndVendor(SystemRootDirectory
, SystemRoot
, L
"System32\\ntoskrnl.exe", &VendorName
);
668 DPRINT1("Kernel file ntoskrnl.exe is either not a PE file, or does not have any vendor?\n");
670 /* The kernel gives the OS its flavour */
673 for (i
= 0; i
< ARRAYSIZE(KnownVendors
); ++i
)
675 Success
= !!FindSubStrI(VendorName
.Buffer
, KnownVendors
[i
]);
678 /* We have found a correct vendor combination */
679 DPRINT1("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors
[i
]);
685 /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */
687 /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */
688 Success
= CheckForValidPEAndVendor(SystemRootDirectory
, SystemRoot
, L
"System32\\ntdll.dll", &VendorName
);
690 DPRINT1("User-mode file ntdll.dll is either not a PE file, or does not have any vendor?\n");
693 for (i
= 0; i
< ARRAYSIZE(KnownVendors
); ++i
)
695 if (!!FindSubStrI(VendorName
.Buffer
, KnownVendors
[i
]))
697 /* We have found a correct vendor combination */
698 DPRINT1("IsValidNTOSInstallation: The user-mode file ntdll.dll is from %S\n", KnownVendors
[i
]);
709 IN PGENERIC_LIST List
)
711 PGENERIC_LIST_ENTRY Entry
;
712 PNTOS_INSTALLATION NtOsInstall
;
713 ULONG NtOsInstallsCount
= GetNumberOfListEntries(List
);
715 DPRINT1("There %s %d installation%s detected:\n",
716 NtOsInstallsCount
>= 2 ? "are" : "is",
718 NtOsInstallsCount
>= 2 ? "s" : "");
720 Entry
= GetFirstListEntry(List
);
723 NtOsInstall
= (PNTOS_INSTALLATION
)GetListEntryUserData(Entry
);
724 Entry
= GetNextListEntry(Entry
);
726 DPRINT1(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot %S\n",
727 NtOsInstall
->DiskNumber
, NtOsInstall
->PartitionNumber
,
728 NtOsInstall
->InstallationName
, NtOsInstall
->SystemRoot
);
734 static PNTOS_INSTALLATION
735 FindExistingNTOSInstall(
736 IN PGENERIC_LIST List
,
738 IN ULONG PartitionNumber
,
739 IN PCWSTR SystemRoot
)
741 PGENERIC_LIST_ENTRY Entry
;
742 PNTOS_INSTALLATION NtOsInstall
;
744 Entry
= GetFirstListEntry(List
);
747 NtOsInstall
= (PNTOS_INSTALLATION
)GetListEntryUserData(Entry
);
748 Entry
= GetNextListEntry(Entry
);
750 if (NtOsInstall
->DiskNumber
== DiskNumber
&&
751 NtOsInstall
->PartitionNumber
== PartitionNumber
&&
752 _wcsicmp(NtOsInstall
->SystemRoot
, SystemRoot
) == 0)
762 static PNTOS_INSTALLATION
764 IN PGENERIC_LIST List
,
766 IN ULONG PartitionNumber
,
767 IN PCWSTR SystemRoot
,
768 IN PCWSTR InstallationName
)
770 PNTOS_INSTALLATION NtOsInstall
;
771 CHAR InstallNameA
[MAX_PATH
];
773 /* Is there already any installation with these settings? */
774 NtOsInstall
= FindExistingNTOSInstall(List
, DiskNumber
, PartitionNumber
, SystemRoot
);
777 DPRINT1("An NTOS installation with name \"%S\" already exists on disk #%d, partition #%d, in SystemRoot %S\n",
778 NtOsInstall
->InstallationName
, NtOsInstall
->DiskNumber
, NtOsInstall
->PartitionNumber
, NtOsInstall
->SystemRoot
);
780 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
781 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
786 /* None was found, so add a new one */
787 NtOsInstall
= RtlAllocateHeap(ProcessHeap
, HEAP_ZERO_MEMORY
, sizeof(*NtOsInstall
));
791 NtOsInstall
->DiskNumber
= DiskNumber
;
792 NtOsInstall
->PartitionNumber
= PartitionNumber
;
793 StringCchCopyW(NtOsInstall
->SystemRoot
, ARRAYSIZE(NtOsInstall
->SystemRoot
), SystemRoot
);
794 StringCchCopyW(NtOsInstall
->InstallationName
, ARRAYSIZE(NtOsInstall
->InstallationName
), InstallationName
);
796 // Having the GENERIC_LIST storing the display item string plainly sucks...
797 StringCchPrintfA(InstallNameA
, ARRAYSIZE(InstallNameA
), "%S", InstallationName
);
798 AppendGenericListEntry(List
, InstallNameA
, NtOsInstall
, FALSE
);
804 FindNTOSInstallations(
805 IN OUT PGENERIC_LIST List
,
806 IN PPARTLIST PartList
,
807 IN PPARTENTRY PartEntry
)
811 HANDLE PartitionHandle
, FileHandle
;
812 OBJECT_ATTRIBUTES ObjectAttributes
;
813 IO_STATUS_BLOCK IoStatusBlock
;
814 UNICODE_STRING PartitionRootPath
;
815 HANDLE SectionHandle
;
819 WCHAR PathBuffer
[MAX_PATH
];
821 PDISKENTRY DiskEntry
= PartEntry
->DiskEntry
;
822 ULONG DiskNumber
= DiskEntry
->DiskNumber
;
823 ULONG PartitionNumber
= PartEntry
->PartitionNumber
;
825 /* Set PartitionRootPath */
826 StringCchPrintfW(PathBuffer
, ARRAYSIZE(PathBuffer
),
827 L
"\\Device\\Harddisk%lu\\Partition%lu\\",
828 DiskNumber
, PartitionNumber
);
829 RtlInitUnicodeString(&PartitionRootPath
, PathBuffer
);
830 DPRINT1("FindNTOSInstallations: PartitionRootPath: %wZ\n", &PartitionRootPath
);
832 /* Open the partition */
833 InitializeObjectAttributes(&ObjectAttributes
,
835 OBJ_CASE_INSENSITIVE
,
838 Status
= NtOpenFile(&PartitionHandle
,
839 FILE_LIST_DIRECTORY
| SYNCHRONIZE
,
842 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
843 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_DIRECTORY_FILE
);
844 if (!NT_SUCCESS(Status
))
846 DPRINT1("Failed to open partition %wZ, Status 0x%08lx\n", &PartitionRootPath
, Status
);
850 /* Try to see whether we recognize some NT boot loaders */
851 for (i
= 0; i
< ARRAYSIZE(NtosBootLoaders
); ++i
)
853 /* Check whether the loader executable exists */
854 if (!DoesFileExist(PartitionHandle
, NULL
, NtosBootLoaders
[i
].LoaderExecutable
))
856 /* The loader does not exist, continue with another one */
857 DPRINT1("Loader executable %S does not exist, continue with another one...\n", NtosBootLoaders
[i
].LoaderExecutable
);
861 /* Check whether the loader configuration file exists */
862 Status
= OpenAndMapFile(PartitionHandle
, NULL
, NtosBootLoaders
[i
].LoaderConfigurationFile
,
863 &FileHandle
, &SectionHandle
, &ViewBase
, &FileSize
);
864 if (!NT_SUCCESS(Status
))
866 /* The loader does not exist, continue with another one */
867 // FIXME: Consider it might be optional??
868 DPRINT1("Loader configuration file %S does not exist, continue with another one...\n", NtosBootLoaders
[i
].LoaderConfigurationFile
);
872 /* The loader configuration file exists, interpret it to find valid installations */
873 DPRINT1("Analyse the OS installations inside '%S' in disk #%d, partition #%d\n",
874 NtosBootLoaders
[i
].LoaderConfigurationFile
, DiskNumber
, PartitionNumber
);
875 switch (NtosBootLoaders
[i
].Type
)
878 Status
= FreeLdrEnumerateInstallations(List
, PartList
, ViewBase
, FileSize
);
882 Status
= NtLdrEnumerateInstallations(List
, PartList
, ViewBase
, FileSize
);
886 DPRINT1("Loader type %d is currently unsupported!\n", NtosBootLoaders
[i
].Type
);
887 Status
= STATUS_SUCCESS
;
890 /* Finally, unmap and close the file */
891 UnMapFile(SectionHandle
, ViewBase
);
895 /* Close the partition */
896 NtClose(PartitionHandle
);
901 ShouldICheckThisPartition(
902 IN PPARTENTRY PartEntry
)
907 return PartEntry
->IsPartitioned
&&
908 !IsContainerPartition(PartEntry
->PartitionType
) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
910 (PartEntry
->FormatState
== Preformatted
/* || PartEntry->FormatState == Formatted */);
913 // EnumerateNTOSInstallations
915 CreateNTOSInstallationsList(
916 IN PPARTLIST PartList
)
919 PLIST_ENTRY Entry
, Entry2
;
920 PDISKENTRY DiskEntry
;
921 PPARTENTRY PartEntry
;
923 List
= CreateGenericList();
927 /* Loop each available disk ... */
928 Entry
= PartList
->DiskListHead
.Flink
;
929 while (Entry
!= &PartList
->DiskListHead
)
931 DiskEntry
= CONTAINING_RECORD(Entry
, DISKENTRY
, ListEntry
);
932 Entry
= Entry
->Flink
;
934 DPRINT1("Disk #%d\n", DiskEntry
->DiskNumber
);
936 /* ... and for each disk, loop each available partition */
938 /* First, the primary partitions */
939 Entry2
= DiskEntry
->PrimaryPartListHead
.Flink
;
940 while (Entry2
!= &DiskEntry
->PrimaryPartListHead
)
942 PartEntry
= CONTAINING_RECORD(Entry2
, PARTENTRY
, ListEntry
);
943 Entry2
= Entry2
->Flink
;
945 ASSERT(PartEntry
->DiskEntry
== DiskEntry
);
947 DPRINT1(" Primary Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
948 PartEntry
->PartitionNumber
, PartEntry
->PartitionIndex
,
949 PartEntry
->PartitionType
, PartEntry
->LogicalPartition
? "TRUE" : "FALSE",
950 PartEntry
->IsPartitioned
? "TRUE" : "FALSE",
951 PartEntry
->New
? "Yes" : "No",
952 PartEntry
->AutoCreate
? "Yes" : "No",
953 PartEntry
->FormatState
,
954 ShouldICheckThisPartition(PartEntry
) ? "YES!" : "NO!");
956 if (ShouldICheckThisPartition(PartEntry
))
957 FindNTOSInstallations(List
, PartList
, PartEntry
);
960 /* Then, the logical partitions (present in the extended partition) */
961 Entry2
= DiskEntry
->LogicalPartListHead
.Flink
;
962 while (Entry2
!= &DiskEntry
->LogicalPartListHead
)
964 PartEntry
= CONTAINING_RECORD(Entry2
, PARTENTRY
, ListEntry
);
965 Entry2
= Entry2
->Flink
;
967 ASSERT(PartEntry
->DiskEntry
== DiskEntry
);
969 DPRINT1(" Logical Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
970 PartEntry
->PartitionNumber
, PartEntry
->PartitionIndex
,
971 PartEntry
->PartitionType
, PartEntry
->LogicalPartition
? "TRUE" : "FALSE",
972 PartEntry
->IsPartitioned
? "TRUE" : "FALSE",
973 PartEntry
->New
? "Yes" : "No",
974 PartEntry
->AutoCreate
? "Yes" : "No",
975 PartEntry
->FormatState
,
976 ShouldICheckThisPartition(PartEntry
) ? "YES!" : "NO!");
978 if (ShouldICheckThisPartition(PartEntry
))
979 FindNTOSInstallations(List
, PartList
, PartEntry
);
983 /**** Debugging: List all the collected installations ****/
984 DumpNTOSInstalls(List
);