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