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