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