xref: /reactos/base/setup/lib/utils/osdetect.c (revision c7295b2c)
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[] = { 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     IN PPARTENTRY PartEntry OPTIONAL);
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     PNTOS_INSTALLATION NtOsInstall;
78 
79     ULONG DiskNumber = 0, PartitionNumber = 0;
80     PCWSTR PathComponent = NULL;
81     PDISKENTRY DiskEntry = NULL;
82     PPARTENTRY PartEntry = NULL;
83 
84     UNICODE_STRING SystemRootPath;
85     WCHAR SystemRoot[MAX_PATH];
86 
87     USHORT Machine;
88     UNICODE_STRING VendorName;
89     WCHAR VendorNameBuffer[MAX_PATH];
90 
91 
92     /* We have a boot entry */
93 
94     /* Check for supported boot type "Windows2003" */
95     if (BootEntry->OsOptionsLength < sizeof(NTOS_OPTIONS) ||
96         RtlCompareMemory(&BootEntry->OsOptions /* Signature */,
97                          NTOS_OPTIONS_SIGNATURE,
98                          RTL_FIELD_SIZE(NTOS_OPTIONS, Signature)) !=
99                          RTL_FIELD_SIZE(NTOS_OPTIONS, Signature))
100     {
101         /* This is not a ReactOS entry */
102         // DPRINT("    An installation '%S' of unsupported type '%S'\n",
103                // BootEntry->FriendlyName, BootEntry->Version ? BootEntry->Version : L"n/a");
104         DPRINT("    An installation '%S' of unsupported type %lu\n",
105                BootEntry->FriendlyName, BootEntry->OsOptionsLength);
106         /* Continue the enumeration */
107         return STATUS_SUCCESS;
108     }
109 
110     /* BootType is Windows2003, now check OsLoadPath */
111     if (!Options->OsLoadPath || !*Options->OsLoadPath)
112     {
113         /* Certainly not a ReactOS installation */
114         DPRINT1("    A Win2k3 install '%S' without an ARC path?!\n", BootEntry->FriendlyName);
115         /* Continue the enumeration */
116         return STATUS_SUCCESS;
117     }
118 
119     DPRINT("    Found a candidate Win2k3 install '%S' with ARC path '%S'\n",
120            BootEntry->FriendlyName, Options->OsLoadPath);
121     // DPRINT("    Found a Win2k3 install '%S' with ARC path '%S'\n",
122            // BootEntry->FriendlyName, Options->OsLoadPath);
123 
124     // TODO: Normalize the ARC path.
125 
126     /*
127      * Check whether we already have an installation with this ARC path.
128      * If this is the case, stop there.
129      */
130     NtOsInstall = FindExistingNTOSInstall(Data->List, Options->OsLoadPath, NULL);
131     if (NtOsInstall)
132     {
133         DPRINT("    An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n",
134                NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemArcPath);
135         /* Continue the enumeration */
136         return STATUS_SUCCESS;
137     }
138 
139     /*
140      * Convert the ARC path into an NT path, from which we will deduce
141      * the real disk drive & partition on which the candidate installation
142      * resides, as well verifying whether it is indeed an NTOS installation.
143      */
144     RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot));
145     if (!ArcPathToNtPath(&SystemRootPath, Options->OsLoadPath, Data->PartList))
146     {
147         DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", Options->OsLoadPath);
148         /* Continue the enumeration */
149         return STATUS_SUCCESS;
150     }
151 
152     DPRINT("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n",
153            Options->OsLoadPath, &SystemRootPath);
154 
155     /*
156      * Check whether we already have an installation with this NT path.
157      * If this is the case, stop there.
158      */
159     NtOsInstall = FindExistingNTOSInstall(Data->List, NULL /*Options->OsLoadPath*/, &SystemRootPath);
160     if (NtOsInstall)
161     {
162         DPRINT1("    An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n",
163                 NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemNtPath);
164         /* Continue the enumeration */
165         return STATUS_SUCCESS;
166     }
167 
168     DPRINT("EnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath);
169 
170     /* Check if this is a valid NTOS installation; stop there if it isn't one */
171     RtlInitEmptyUnicodeString(&VendorName, VendorNameBuffer, sizeof(VendorNameBuffer));
172     if (!IsValidNTOSInstallation(&SystemRootPath, &Machine, &VendorName))
173     {
174         /* Continue the enumeration */
175         return STATUS_SUCCESS;
176     }
177 
178     DPRINT("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n",
179            Options->OsLoadPath, &SystemRootPath);
180 
181     /* From the NT path, compute the disk, partition and path components */
182     if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
183     {
184         DPRINT("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
185                &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
186 
187         /* Retrieve the corresponding disk and partition */
188         if (!GetDiskOrPartition(Data->PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
189         {
190             DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n",
191                     DiskNumber, PartitionNumber);
192         }
193     }
194     else
195     {
196         DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
197     }
198 
199     /* Add the discovered NTOS installation into the list */
200     AddNTOSInstallation(Data->List,
201                         BootEntry->FriendlyName,
202                         Machine,
203                         VendorName.Buffer, // FIXME: What if it's not NULL-terminated?
204                         Options->OsLoadPath,
205                         &SystemRootPath, PathComponent,
206                         DiskNumber, PartitionNumber, PartEntry);
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     IN PPARTENTRY PartEntry OPTIONAL)
620 {
621     PNTOS_INSTALLATION NtOsInstall;
622     SIZE_T ArcPathLength, NtPathLength;
623 
624     /* Is there already any installation with these settings? */
625     NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath);
626     if (NtOsInstall)
627     {
628         DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n",
629                 NtOsInstall->InstallationName, NtOsInstall->VendorName,
630                 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath);
631         //
632         // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
633         // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
634         //
635         return NtOsInstall;
636     }
637 
638     ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR);
639     // NtPathLength  = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR));
640     NtPathLength  = SystemRootNtPath->Length + sizeof(UNICODE_NULL);
641 
642     /* None was found, so add a new one */
643     NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY,
644                                   sizeof(*NtOsInstall) +
645                                   ArcPathLength + NtPathLength);
646     if (!NtOsInstall)
647         return NULL;
648 
649     NtOsInstall->DiskNumber = DiskNumber;
650     NtOsInstall->PartitionNumber = PartitionNumber;
651     NtOsInstall->PartEntry = PartEntry;
652 
653     NtOsInstall->Machine = Machine;
654 
655     RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath,
656                               (PWCHAR)(NtOsInstall + 1),
657                               ArcPathLength);
658     RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength);
659     NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL);
660 
661     RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath,
662                               (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength),
663                               NtPathLength);
664     RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath);
665     NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer +
666                                     (PathComponent - SystemRootNtPath->Buffer);
667 
668     RtlStringCchCopyW(NtOsInstall->InstallationName,
669                       ARRAYSIZE(NtOsInstall->InstallationName),
670                       InstallationName);
671 
672     RtlStringCchCopyW(NtOsInstall->VendorName,
673                       ARRAYSIZE(NtOsInstall->VendorName),
674                       VendorName);
675 
676     AppendGenericListEntry(List, NtOsInstall, FALSE);
677 
678     return NtOsInstall;
679 }
680 
681 static VOID
682 FindNTOSInstallations(
683     IN OUT PGENERIC_LIST List,
684     IN PPARTLIST PartList,
685     IN PPARTENTRY PartEntry)
686 {
687     NTSTATUS Status;
688     ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber;
689     ULONG PartitionNumber = PartEntry->PartitionNumber;
690     HANDLE PartitionDirectoryHandle;
691     OBJECT_ATTRIBUTES ObjectAttributes;
692     IO_STATUS_BLOCK IoStatusBlock;
693     UNICODE_STRING PartitionRootPath;
694     BOOT_STORE_TYPE Type;
695     PVOID BootStoreHandle;
696     ENUM_INSTALLS_DATA Data;
697     ULONG Version;
698     WCHAR PathBuffer[MAX_PATH];
699 
700     ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0);
701 
702     /* Set PartitionRootPath */
703     RtlStringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer),
704                         L"\\Device\\Harddisk%lu\\Partition%lu\\",
705                         DiskNumber, PartitionNumber);
706     RtlInitUnicodeString(&PartitionRootPath, PathBuffer);
707     DPRINT("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath);
708 
709     /* Open the partition */
710     InitializeObjectAttributes(&ObjectAttributes,
711                                &PartitionRootPath,
712                                OBJ_CASE_INSENSITIVE,
713                                NULL,
714                                NULL);
715     Status = NtOpenFile(&PartitionDirectoryHandle,
716                         FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
717                         &ObjectAttributes,
718                         &IoStatusBlock,
719                         FILE_SHARE_READ | FILE_SHARE_WRITE,
720                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
721     if (!NT_SUCCESS(Status))
722     {
723         DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status);
724         return;
725     }
726 
727     Data.List = List;
728     Data.PartList = PartList;
729 
730     /* Try to see whether we recognize some NT boot loaders */
731     for (Type = FreeLdr; Type < BldrTypeMax; ++Type)
732     {
733         Status = FindBootStore(PartitionDirectoryHandle, Type, &Version);
734         if (!NT_SUCCESS(Status))
735         {
736             /* The loader does not exist, continue with another one */
737             DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n",
738                    Type, Status);
739             continue;
740         }
741 
742         /* The loader exists, try to enumerate its boot entries */
743         DPRINT("Analyze the OS installations for loader type '%d' in disk #%d, partition #%d\n",
744                Type, DiskNumber, PartitionNumber);
745 
746         Status = OpenBootStoreByHandle(&BootStoreHandle, PartitionDirectoryHandle, Type,
747                                        BS_OpenExisting, BS_ReadAccess);
748         if (!NT_SUCCESS(Status))
749         {
750             DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n",
751                     Type, Status);
752             continue;
753         }
754         EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data);
755         CloseBootStore(BootStoreHandle);
756     }
757 
758     /* Close the partition */
759     NtClose(PartitionDirectoryHandle);
760 }
761 
762 // static
763 FORCEINLINE BOOLEAN
764 ShouldICheckThisPartition(
765     IN PPARTENTRY PartEntry)
766 {
767     if (!PartEntry)
768         return FALSE;
769 
770     return PartEntry->IsPartitioned &&
771            !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
772            !PartEntry->New &&
773            (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */);
774 }
775 
776 // EnumerateNTOSInstallations
777 PGENERIC_LIST
778 CreateNTOSInstallationsList(
779     IN PPARTLIST PartList)
780 {
781     PGENERIC_LIST List;
782     PLIST_ENTRY Entry, Entry2;
783     PDISKENTRY DiskEntry;
784     PPARTENTRY PartEntry;
785 
786     List = CreateGenericList();
787     if (List == NULL)
788         return NULL;
789 
790     /* Loop each available disk ... */
791     Entry = PartList->DiskListHead.Flink;
792     while (Entry != &PartList->DiskListHead)
793     {
794         DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
795         Entry = Entry->Flink;
796 
797         DPRINT("Disk #%d\n", DiskEntry->DiskNumber);
798 
799         /* ... and for each disk, loop each available partition */
800 
801         /* First, the primary partitions */
802         Entry2 = DiskEntry->PrimaryPartListHead.Flink;
803         while (Entry2 != &DiskEntry->PrimaryPartListHead)
804         {
805             PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
806             Entry2 = Entry2->Flink;
807 
808             ASSERT(PartEntry->DiskEntry == DiskEntry);
809 
810             DPRINT("   Primary Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, FormatState = %lu -- Should I check it? %s\n",
811                    PartEntry->PartitionNumber, PartEntry->PartitionIndex,
812                    PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
813                    PartEntry->IsPartitioned ? "TRUE" : "FALSE",
814                    PartEntry->New ? "Yes" : "No",
815                    PartEntry->FormatState,
816                    ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
817 
818             if (ShouldICheckThisPartition(PartEntry))
819                 FindNTOSInstallations(List, PartList, PartEntry);
820         }
821 
822         /* Then, the logical partitions (present in the extended partition) */
823         Entry2 = DiskEntry->LogicalPartListHead.Flink;
824         while (Entry2 != &DiskEntry->LogicalPartListHead)
825         {
826             PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
827             Entry2 = Entry2->Flink;
828 
829             ASSERT(PartEntry->DiskEntry == DiskEntry);
830 
831             DPRINT("   Logical Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, FormatState = %lu -- Should I check it? %s\n",
832                    PartEntry->PartitionNumber, PartEntry->PartitionIndex,
833                    PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
834                    PartEntry->IsPartitioned ? "TRUE" : "FALSE",
835                    PartEntry->New ? "Yes" : "No",
836                    PartEntry->FormatState,
837                    ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
838 
839             if (ShouldICheckThisPartition(PartEntry))
840                 FindNTOSInstallations(List, PartList, PartEntry);
841         }
842     }
843 
844 #ifndef NDEBUG
845     /**** Debugging: List all the collected installations ****/
846     DumpNTOSInstalls(List);
847 #endif
848 
849     return List;
850 }
851 
852 /* EOF */
853