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