xref: /reactos/base/setup/lib/utils/osdetect.c (revision d7c1d220)
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
EnumerateInstallations(IN BOOT_STORE_TYPE Type,IN PBOOT_STORE_ENTRY BootEntry,IN PVOID Parameter OPTIONAL)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  * @brief
214  * Finds the first occurrence of a sub-string 'strSearch' inside 'str',
215  * using case-insensitive comparisons.
216  **/
217 PCWSTR
218 NTAPI
FindSubStrI(_In_ PCWSTR str,_In_ PCWSTR strSearch)219 FindSubStrI(
220     _In_ PCWSTR str,
221     _In_ 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
CheckForValidPEAndVendor(IN HANDLE RootDirectory OPTIONAL,IN PCWSTR PathNameToFile,OUT PUSHORT Machine,OUT PUNICODE_STRING VendorName)247 CheckForValidPEAndVendor(
248     IN HANDLE RootDirectory OPTIONAL,
249     IN PCWSTR PathNameToFile,
250     OUT PUSHORT Machine,
251     OUT PUNICODE_STRING VendorName)
252 {
253     BOOLEAN Success = FALSE;
254     NTSTATUS Status;
255     HANDLE FileHandle, SectionHandle;
256     // SIZE_T ViewSize;
257     PVOID ViewBase;
258     PIMAGE_NT_HEADERS NtHeader;
259     PVOID VersionBuffer = NULL; // Read-only
260     PVOID pvData = NULL;
261     UINT BufLen = 0;
262 
263     if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
264         return FALSE;
265 
266     *VendorName->Buffer = UNICODE_NULL;
267     VendorName->Length = 0;
268 
269     Status = OpenAndMapFile(RootDirectory, PathNameToFile,
270                             &FileHandle, NULL,
271                             &SectionHandle, &ViewBase, FALSE);
272     if (!NT_SUCCESS(Status))
273     {
274         DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status);
275         return FALSE; // Status;
276     }
277 
278     /* Make sure it's a valid NT PE file */
279     NtHeader = RtlImageNtHeader(ViewBase);
280     if (!NtHeader)
281     {
282         DPRINT1("File '%S' does not seem to be a valid NT PE file, bail out\n", PathNameToFile);
283         Status = STATUS_INVALID_IMAGE_FORMAT;
284         goto UnmapCloseFile;
285     }
286 
287     /* Retrieve the target architecture of this PE module */
288     *Machine = NtHeader->FileHeader.Machine;
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             DPRINT("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 = (USHORT)wcslen(VendorName->Buffer) * sizeof(WCHAR);
328 
329             Success = TRUE;
330         }
331     }
332 
333     if (!NT_SUCCESS(Status))
334         DPRINT("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
IsValidNTOSInstallationByHandle(IN HANDLE SystemRootDirectory,OUT PUSHORT Machine OPTIONAL,OUT PUNICODE_STRING VendorName OPTIONAL)350 IsValidNTOSInstallationByHandle(
351     IN HANDLE SystemRootDirectory,
352     OUT PUSHORT Machine OPTIONAL,
353     OUT PUNICODE_STRING VendorName OPTIONAL)
354 {
355     BOOLEAN Success = FALSE;
356     PCWSTR PathName;
357     USHORT i;
358     USHORT LocalMachine;
359     UNICODE_STRING LocalVendorName;
360     WCHAR VendorNameBuffer[MAX_PATH];
361 
362     /* Check for VendorName validity */
363     if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
364     {
365         /* Don't use it, invalidate the pointer */
366         VendorName = NULL;
367     }
368     else
369     {
370         /* Zero it out */
371         *VendorName->Buffer = UNICODE_NULL;
372         VendorName->Length = 0;
373     }
374 
375     /* Check for the existence of \SystemRoot\System32 */
376     PathName = L"System32\\";
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     /* Check for the existence of \SystemRoot\System32\drivers */
384     PathName = L"System32\\drivers\\";
385     if (!DoesDirExist(SystemRootDirectory, PathName))
386     {
387         // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status);
388         return FALSE;
389     }
390 
391     /* Check for the existence of \SystemRoot\System32\config */
392     PathName = L"System32\\config\\";
393     if (!DoesDirExist(SystemRootDirectory, PathName))
394     {
395         // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status);
396         return FALSE;
397     }
398 
399 #if 0
400     /*
401      * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config
402      * (but we don't check here whether they are actually valid).
403      */
404     PathName = L"System32\\config\\SYSTEM";
405     if (!DoesFileExist(SystemRootDirectory, PathName))
406     {
407         // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status);
408         return FALSE;
409     }
410     PathName = L"System32\\config\\SOFTWARE";
411     if (!DoesFileExist(SystemRootDirectory, PathName))
412     {
413         // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status);
414         return FALSE;
415     }
416 #endif
417 
418     RtlInitEmptyUnicodeString(&LocalVendorName, VendorNameBuffer, sizeof(VendorNameBuffer));
419 
420     /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */
421     PathName = L"System32\\ntoskrnl.exe";
422     Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName);
423     if (!Success)
424         DPRINT1("Kernel executable '%S' is either not a PE file, or does not have any vendor?\n", PathName);
425 
426     /*
427      * The kernel gives the OS its flavour. If we failed due to the absence of
428      * ntoskrnl.exe this might be due to the fact this particular installation
429      * uses a custom kernel that has a different name, overridden in the boot
430      * parameters. We then rely on the existence of ntdll.dll, which cannot be
431      * renamed on a valid NT system.
432      */
433     if (Success)
434     {
435         for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
436         {
437             Success = !!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]);
438             if (Success)
439             {
440                 /* We have found a correct vendor combination */
441                 DPRINT("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]);
442                 break;
443             }
444         }
445 
446         /* Return the target architecture */
447         if (Machine)
448         {
449             /* Copy the value and invalidate the pointer */
450             *Machine = LocalMachine;
451             Machine = NULL;
452         }
453 
454         /* Return the vendor name */
455         if (VendorName)
456         {
457             /* Copy the string and invalidate the pointer */
458             RtlCopyUnicodeString(VendorName, &LocalVendorName);
459             VendorName = NULL;
460         }
461     }
462 
463     /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */
464 
465     /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */
466     PathName = L"System32\\ntdll.dll";
467     Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName);
468     if (!Success)
469         DPRINT1("User-mode DLL '%S' is either not a PE file, or does not have any vendor?\n", PathName);
470 
471     if (Success)
472     {
473         for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
474         {
475             if (!!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]))
476             {
477                 /* We have found a correct vendor combination */
478                 DPRINT("IsValidNTOSInstallation: The user-mode DLL '%S' is from %S\n", PathName, KnownVendors[i]);
479                 break;
480             }
481         }
482 
483         /* Return the target architecture if not already obtained */
484         if (Machine)
485         {
486             /* Copy the value and invalidate the pointer */
487             *Machine = LocalMachine;
488             Machine = NULL;
489         }
490 
491         /* Return the vendor name if not already obtained */
492         if (VendorName)
493         {
494             /* Copy the string and invalidate the pointer */
495             RtlCopyUnicodeString(VendorName, &LocalVendorName);
496             VendorName = NULL;
497         }
498     }
499 
500     return Success;
501 }
502 
503 static BOOLEAN
IsValidNTOSInstallation(IN PUNICODE_STRING SystemRootPath,OUT PUSHORT Machine,OUT PUNICODE_STRING VendorName OPTIONAL)504 IsValidNTOSInstallation(
505     IN PUNICODE_STRING SystemRootPath,
506     OUT PUSHORT Machine,
507     OUT PUNICODE_STRING VendorName OPTIONAL)
508 {
509     NTSTATUS Status;
510     OBJECT_ATTRIBUTES ObjectAttributes;
511     IO_STATUS_BLOCK IoStatusBlock;
512     HANDLE SystemRootDirectory;
513     BOOLEAN Success;
514 
515     /* Open SystemRootPath */
516     InitializeObjectAttributes(&ObjectAttributes,
517                                SystemRootPath,
518                                OBJ_CASE_INSENSITIVE,
519                                NULL,
520                                NULL);
521     Status = NtOpenFile(&SystemRootDirectory,
522                         FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
523                         &ObjectAttributes,
524                         &IoStatusBlock,
525                         FILE_SHARE_READ | FILE_SHARE_WRITE,
526                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
527     if (!NT_SUCCESS(Status))
528     {
529         DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", SystemRootPath, Status);
530         return FALSE;
531     }
532 
533     Success = IsValidNTOSInstallationByHandle(SystemRootDirectory,
534                                               Machine, VendorName);
535 
536     /* Done! */
537     NtClose(SystemRootDirectory);
538     return Success;
539 }
540 
541 #ifndef NDEBUG
542 static VOID
DumpNTOSInstalls(IN PGENERIC_LIST List)543 DumpNTOSInstalls(
544     IN PGENERIC_LIST List)
545 {
546     PGENERIC_LIST_ENTRY Entry;
547     PNTOS_INSTALLATION NtOsInstall;
548     ULONG NtOsInstallsCount = GetNumberOfListEntries(List);
549 
550     DPRINT("There %s %d installation%s detected:\n",
551            NtOsInstallsCount >= 2 ? "are" : "is",
552            NtOsInstallsCount,
553            NtOsInstallsCount >= 2 ? "s" : "");
554 
555     for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry))
556     {
557         NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
558 
559         DPRINT("    On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n",
560                NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber,
561                NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath);
562     }
563 
564     DPRINT("Done.\n");
565 }
566 #endif
567 
568 static PNTOS_INSTALLATION
FindExistingNTOSInstall(IN PGENERIC_LIST List,IN PCWSTR SystemRootArcPath OPTIONAL,IN PUNICODE_STRING SystemRootNtPath OPTIONAL)569 FindExistingNTOSInstall(
570     IN PGENERIC_LIST List,
571     IN PCWSTR SystemRootArcPath OPTIONAL,
572     IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
573     )
574 {
575     PGENERIC_LIST_ENTRY Entry;
576     PNTOS_INSTALLATION NtOsInstall;
577     UNICODE_STRING SystemArcPath;
578 
579     /*
580      * We search either via ARC path or NT path.
581      * If both pointers are NULL then we fail straight away.
582      */
583     if (!SystemRootArcPath && !SystemRootNtPath)
584         return NULL;
585 
586     RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath);
587 
588     for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry))
589     {
590         NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
591 
592         /*
593          * Note that if both ARC paths are equal, then the corresponding
594          * NT paths must be the same. However, two ARC paths may be different
595          * but resolve into the same NT path.
596          */
597         if ( (SystemRootArcPath &&
598               RtlEqualUnicodeString(&NtOsInstall->SystemArcPath,
599                                     &SystemArcPath, TRUE)) ||
600              (SystemRootNtPath  &&
601               RtlEqualUnicodeString(&NtOsInstall->SystemNtPath,
602                                     SystemRootNtPath, TRUE)) )
603         {
604             /* Found it! */
605             return NtOsInstall;
606         }
607     }
608 
609     return NULL;
610 }
611 
612 static PNTOS_INSTALLATION
AddNTOSInstallation(_In_ PGENERIC_LIST List,_In_ PCWSTR InstallationName,_In_ USHORT Machine,_In_ PCWSTR VendorName,_In_ PCWSTR SystemRootArcPath,_In_ PUNICODE_STRING SystemRootNtPath,_In_ PCWSTR PathComponent,_In_ ULONG DiskNumber,_In_ ULONG PartitionNumber)613 AddNTOSInstallation(
614     _In_ PGENERIC_LIST List,
615     _In_ PCWSTR InstallationName,
616     _In_ USHORT Machine,
617     _In_ PCWSTR VendorName,
618     _In_ PCWSTR SystemRootArcPath,
619     _In_ PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
620     _In_ PCWSTR PathComponent,    // Pointer inside SystemRootNtPath buffer
621     _In_ ULONG DiskNumber,
622     _In_ ULONG PartitionNumber)
623 {
624     PNTOS_INSTALLATION NtOsInstall;
625     SIZE_T ArcPathLength, NtPathLength;
626 
627     /* Is there already any installation with these settings? */
628     NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath);
629     if (NtOsInstall)
630     {
631         DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n",
632                 NtOsInstall->InstallationName, NtOsInstall->VendorName,
633                 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath);
634         //
635         // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
636         // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
637         //
638         return NtOsInstall;
639     }
640 
641     ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR);
642     // NtPathLength  = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR));
643     NtPathLength  = SystemRootNtPath->Length + sizeof(UNICODE_NULL);
644 
645     /* None was found, so add a new one */
646     NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY,
647                                   sizeof(*NtOsInstall) +
648                                   ArcPathLength + NtPathLength);
649     if (!NtOsInstall)
650         return NULL;
651 
652     NtOsInstall->DiskNumber = DiskNumber;
653     NtOsInstall->PartitionNumber = PartitionNumber;
654     NtOsInstall->Machine = Machine;
655 
656     RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath,
657                               (PWCHAR)(NtOsInstall + 1),
658                               ArcPathLength);
659     RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength);
660     NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL);
661 
662     RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath,
663                               (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength),
664                               NtPathLength);
665     RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath);
666     NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer +
667                                     (PathComponent - SystemRootNtPath->Buffer);
668 
669     RtlStringCchCopyW(NtOsInstall->InstallationName,
670                       ARRAYSIZE(NtOsInstall->InstallationName),
671                       InstallationName);
672 
673     RtlStringCchCopyW(NtOsInstall->VendorName,
674                       ARRAYSIZE(NtOsInstall->VendorName),
675                       VendorName);
676 
677     AppendGenericListEntry(List, NtOsInstall, FALSE);
678 
679     return NtOsInstall;
680 }
681 
682 static VOID
FindNTOSInstallations(_Inout_ PGENERIC_LIST List,_In_ PPARTLIST PartList,_In_ PVOLENTRY Volume)683 FindNTOSInstallations(
684     _Inout_ PGENERIC_LIST List,
685     _In_ PPARTLIST PartList,
686     _In_ PVOLENTRY Volume)
687 {
688     NTSTATUS Status;
689     HANDLE VolumeRootDirHandle;
690     OBJECT_ATTRIBUTES ObjectAttributes;
691     IO_STATUS_BLOCK IoStatusBlock;
692     UNICODE_STRING VolumeRootPath;
693     BOOT_STORE_TYPE Type;
694     PVOID BootStoreHandle;
695     ENUM_INSTALLS_DATA Data;
696     ULONG Version;
697     WCHAR PathBuffer[RTL_NUMBER_OF_FIELD(VOLINFO, DeviceName) + 1];
698 
699     /* Set VolumeRootPath */
700     RtlStringCchPrintfW(PathBuffer, _countof(PathBuffer),
701                         L"%s\\", Volume->Info.DeviceName);
702     RtlInitUnicodeString(&VolumeRootPath, PathBuffer);
703     DPRINT("FindNTOSInstallations(%wZ)\n", &VolumeRootPath);
704 
705     /* Open the volume */
706     InitializeObjectAttributes(&ObjectAttributes,
707                                &VolumeRootPath,
708                                OBJ_CASE_INSENSITIVE,
709                                NULL,
710                                NULL);
711     Status = NtOpenFile(&VolumeRootDirHandle,
712                         FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
713                         &ObjectAttributes,
714                         &IoStatusBlock,
715                         FILE_SHARE_READ | FILE_SHARE_WRITE,
716                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
717     if (!NT_SUCCESS(Status))
718     {
719         DPRINT1("Failed to open volume '%wZ', Status 0x%08lx\n", &VolumeRootPath, Status);
720         return;
721     }
722 
723     Data.List = List;
724     Data.PartList = PartList;
725 
726     /* Try to see whether we recognize some NT boot loaders */
727     for (Type = FreeLdr; Type < BldrTypeMax; ++Type)
728     {
729         Status = FindBootStore(VolumeRootDirHandle, Type, &Version);
730         if (!NT_SUCCESS(Status))
731         {
732             /* The loader does not exist, continue with another one */
733             DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n",
734                    Type, Status);
735             continue;
736         }
737 
738         /* The loader exists, try to enumerate its boot entries */
739         DPRINT("Analyze the OS installations for loader type '%d' in Volume %wZ (Disk #%d, Partition #%d)\n",
740                Type, &VolumeRootPath,
741                Volume->PartEntry->DiskEntry->DiskNumber,
742                Volume->PartEntry->PartitionNumber);
743 
744         Status = OpenBootStoreByHandle(&BootStoreHandle, VolumeRootDirHandle, Type,
745                                        BS_OpenExisting, BS_ReadAccess);
746         if (!NT_SUCCESS(Status))
747         {
748             DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n",
749                     Type, Status);
750             continue;
751         }
752         EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data);
753         CloseBootStore(BootStoreHandle);
754     }
755 
756     /* Close the volume */
757     NtClose(VolumeRootDirHandle);
758 }
759 
760 /**
761  * @brief
762  * Create a list of available NT OS installations on the computer,
763  * by searching for recognized ones on each recognized storage volume.
764  **/
765 // EnumerateNTOSInstallations
766 PGENERIC_LIST
767 NTAPI
CreateNTOSInstallationsList(_In_ PPARTLIST PartList)768 CreateNTOSInstallationsList(
769     _In_ PPARTLIST PartList)
770 {
771     PGENERIC_LIST List;
772     PLIST_ENTRY Entry;
773     PVOLENTRY Volume;
774     BOOLEAN CheckVolume;
775 
776     List = CreateGenericList();
777     if (!List)
778         return NULL;
779 
780     /* Loop each available volume */
781     for (Entry = PartList->VolumesList.Flink;
782          Entry != &PartList->VolumesList;
783          Entry = Entry->Flink)
784     {
785         Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry);
786         /* Valid OS installations can be found only on basic volumes */
787         if (!Volume->PartEntry) // TODO: In the future: (!Volume->IsSimpleVolume)
788             continue;
789 
790         CheckVolume = (!Volume->New && (Volume->FormatState == Formatted));
791 
792 #ifndef NDEBUG
793         {
794         PPARTENTRY PartEntry = Volume->PartEntry;
795         ASSERT(PartEntry->Volume == Volume);
796         DPRINT("Volume %S (%c%c) on Disk #%d, Partition #%d (%s), "
797                "index %d - Type 0x%02x, IsVolNew = %s, FormatState = %lu -- Should I check it? %s\n",
798                Volume->Info.DeviceName,
799                !Volume->Info.DriveLetter ? '-' : (CHAR)Volume->Info.DriveLetter,
800                !Volume->Info.DriveLetter ? '-' : ':',
801                PartEntry->DiskEntry->DiskNumber,
802                PartEntry->PartitionNumber,
803                PartEntry->LogicalPartition ? "Logical" : "Primary",
804                PartEntry->PartitionIndex,
805                PartEntry->PartitionType,
806                Volume->New ? "Yes" : "No",
807                Volume->FormatState,
808                CheckVolume ? "YES!" : "NO!");
809         }
810 #endif
811 
812         if (CheckVolume)
813             FindNTOSInstallations(List, PartList, Volume);
814     }
815 
816 #ifndef NDEBUG
817     /**** Debugging: List all the collected installations ****/
818     DumpNTOSInstalls(List);
819 #endif
820 
821     return List;
822 }
823 
824 /* EOF */
825