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