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