ed5e1f5805483dafe952d479387617bc61daa88e
[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 // DPRINT(" An installation '%S' of unsupported type '%S'\n",
100 // BootEntry->FriendlyName, BootEntry->Version ? BootEntry->Version : L"n/a");
101 DPRINT(" 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 DPRINT(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n",
117 BootEntry->FriendlyName, Options->OsLoadPath);
118 // DPRINT(" 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 DPRINT(" 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 DPRINT("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 DPRINT("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 DPRINT("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 DPRINT("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 DPRINT("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 DPRINT("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 DPRINT("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 DPRINT("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 #ifndef NDEBUG
502 static VOID
503 DumpNTOSInstalls(
504 IN PGENERIC_LIST List)
505 {
506 PGENERIC_LIST_ENTRY Entry;
507 PNTOS_INSTALLATION NtOsInstall;
508 ULONG NtOsInstallsCount = GetNumberOfListEntries(List);
509
510 DPRINT("There %s %d installation%s detected:\n",
511 NtOsInstallsCount >= 2 ? "are" : "is",
512 NtOsInstallsCount,
513 NtOsInstallsCount >= 2 ? "s" : "");
514
515 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry))
516 {
517 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
518
519 DPRINT(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n",
520 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber,
521 NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath);
522 }
523
524 DPRINT("Done.\n");
525 }
526 #endif
527
528 static PNTOS_INSTALLATION
529 FindExistingNTOSInstall(
530 IN PGENERIC_LIST List,
531 IN PCWSTR SystemRootArcPath OPTIONAL,
532 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
533 )
534 {
535 PGENERIC_LIST_ENTRY Entry;
536 PNTOS_INSTALLATION NtOsInstall;
537 UNICODE_STRING SystemArcPath;
538
539 /*
540 * We search either via ARC path or NT path.
541 * If both pointers are NULL then we fail straight away.
542 */
543 if (!SystemRootArcPath && !SystemRootNtPath)
544 return NULL;
545
546 RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath);
547
548 for (Entry = GetFirstListEntry(List); Entry; Entry = GetNextListEntry(Entry))
549 {
550 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
551
552 /*
553 * Note that if both ARC paths are equal, then the corresponding
554 * NT paths must be the same. However, two ARC paths may be different
555 * but resolve into the same NT path.
556 */
557 if ( (SystemRootArcPath &&
558 RtlEqualUnicodeString(&NtOsInstall->SystemArcPath,
559 &SystemArcPath, TRUE)) ||
560 (SystemRootNtPath &&
561 RtlEqualUnicodeString(&NtOsInstall->SystemNtPath,
562 SystemRootNtPath, TRUE)) )
563 {
564 /* Found it! */
565 return NtOsInstall;
566 }
567 }
568
569 return NULL;
570 }
571
572 static PNTOS_INSTALLATION
573 AddNTOSInstallation(
574 IN PGENERIC_LIST List,
575 IN PCWSTR InstallationName,
576 IN PCWSTR VendorName,
577 IN PCWSTR SystemRootArcPath,
578 IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
579 IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer
580 IN ULONG DiskNumber,
581 IN ULONG PartitionNumber,
582 IN PPARTENTRY PartEntry OPTIONAL)
583 {
584 PNTOS_INSTALLATION NtOsInstall;
585 SIZE_T ArcPathLength, NtPathLength;
586
587 /* Is there already any installation with these settings? */
588 NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath);
589 if (NtOsInstall)
590 {
591 DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n",
592 NtOsInstall->InstallationName, NtOsInstall->VendorName,
593 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath);
594 //
595 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
596 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
597 //
598 return NtOsInstall;
599 }
600
601 ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR);
602 // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR));
603 NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL);
604
605 /* None was found, so add a new one */
606 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY,
607 sizeof(*NtOsInstall) +
608 ArcPathLength + NtPathLength);
609 if (!NtOsInstall)
610 return NULL;
611
612 NtOsInstall->DiskNumber = DiskNumber;
613 NtOsInstall->PartitionNumber = PartitionNumber;
614 NtOsInstall->PartEntry = PartEntry;
615
616 RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath,
617 (PWCHAR)(NtOsInstall + 1),
618 ArcPathLength);
619 RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength);
620 NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL);
621
622 RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath,
623 (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength),
624 NtPathLength);
625 RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath);
626 NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer +
627 (PathComponent - SystemRootNtPath->Buffer);
628
629 RtlStringCchCopyW(NtOsInstall->InstallationName,
630 ARRAYSIZE(NtOsInstall->InstallationName),
631 InstallationName);
632
633 RtlStringCchCopyW(NtOsInstall->VendorName,
634 ARRAYSIZE(NtOsInstall->VendorName),
635 VendorName);
636
637 AppendGenericListEntry(List, NtOsInstall, FALSE);
638
639 return NtOsInstall;
640 }
641
642 static VOID
643 FindNTOSInstallations(
644 IN OUT PGENERIC_LIST List,
645 IN PPARTLIST PartList,
646 IN PPARTENTRY PartEntry)
647 {
648 NTSTATUS Status;
649 ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber;
650 ULONG PartitionNumber = PartEntry->PartitionNumber;
651 HANDLE PartitionDirectoryHandle;
652 OBJECT_ATTRIBUTES ObjectAttributes;
653 IO_STATUS_BLOCK IoStatusBlock;
654 UNICODE_STRING PartitionRootPath;
655 BOOT_STORE_TYPE Type;
656 PVOID BootStoreHandle;
657 ENUM_INSTALLS_DATA Data;
658 ULONG Version;
659 WCHAR PathBuffer[MAX_PATH];
660
661 /* Set PartitionRootPath */
662 RtlStringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer),
663 L"\\Device\\Harddisk%lu\\Partition%lu\\",
664 DiskNumber, PartitionNumber);
665 RtlInitUnicodeString(&PartitionRootPath, PathBuffer);
666 DPRINT("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath);
667
668 /* Open the partition */
669 InitializeObjectAttributes(&ObjectAttributes,
670 &PartitionRootPath,
671 OBJ_CASE_INSENSITIVE,
672 NULL,
673 NULL);
674 Status = NtOpenFile(&PartitionDirectoryHandle,
675 FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
676 &ObjectAttributes,
677 &IoStatusBlock,
678 FILE_SHARE_READ | FILE_SHARE_WRITE,
679 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
680 if (!NT_SUCCESS(Status))
681 {
682 DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status);
683 return;
684 }
685
686 Data.List = List;
687 Data.PartList = PartList;
688
689 /* Try to see whether we recognize some NT boot loaders */
690 for (Type = FreeLdr; Type < BldrTypeMax; ++Type)
691 {
692 Status = FindBootStore(PartitionDirectoryHandle, Type, &Version);
693 if (!NT_SUCCESS(Status))
694 {
695 /* The loader does not exist, continue with another one */
696 DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n",
697 Type, Status);
698 continue;
699 }
700
701 /* The loader exists, try to enumerate its boot entries */
702 DPRINT("Analyze the OS installations for loader type '%d' in disk #%d, partition #%d\n",
703 Type, DiskNumber, PartitionNumber);
704
705 Status = OpenBootStoreByHandle(&BootStoreHandle, PartitionDirectoryHandle, Type, FALSE);
706 if (!NT_SUCCESS(Status))
707 {
708 DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n",
709 Type, Status);
710 continue;
711 }
712 EnumerateBootStoreEntries(BootStoreHandle, EnumerateInstallations, &Data);
713 CloseBootStore(BootStoreHandle);
714 }
715
716 /* Close the partition */
717 NtClose(PartitionDirectoryHandle);
718 }
719
720 // static
721 FORCEINLINE BOOLEAN
722 ShouldICheckThisPartition(
723 IN PPARTENTRY PartEntry)
724 {
725 if (!PartEntry)
726 return FALSE;
727
728 return PartEntry->IsPartitioned &&
729 !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
730 !PartEntry->New &&
731 (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */);
732 }
733
734 // EnumerateNTOSInstallations
735 PGENERIC_LIST
736 CreateNTOSInstallationsList(
737 IN PPARTLIST PartList)
738 {
739 PGENERIC_LIST List;
740 PLIST_ENTRY Entry, Entry2;
741 PDISKENTRY DiskEntry;
742 PPARTENTRY PartEntry;
743
744 List = CreateGenericList();
745 if (List == NULL)
746 return NULL;
747
748 /* Loop each available disk ... */
749 Entry = PartList->DiskListHead.Flink;
750 while (Entry != &PartList->DiskListHead)
751 {
752 DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
753 Entry = Entry->Flink;
754
755 DPRINT("Disk #%d\n", DiskEntry->DiskNumber);
756
757 /* ... and for each disk, loop each available partition */
758
759 /* First, the primary partitions */
760 Entry2 = DiskEntry->PrimaryPartListHead.Flink;
761 while (Entry2 != &DiskEntry->PrimaryPartListHead)
762 {
763 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
764 Entry2 = Entry2->Flink;
765
766 ASSERT(PartEntry->DiskEntry == DiskEntry);
767
768 DPRINT(" Primary Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
769 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
770 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
771 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
772 PartEntry->New ? "Yes" : "No",
773 PartEntry->AutoCreate ? "Yes" : "No",
774 PartEntry->FormatState,
775 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
776
777 if (ShouldICheckThisPartition(PartEntry))
778 FindNTOSInstallations(List, PartList, PartEntry);
779 }
780
781 /* Then, the logical partitions (present in the extended partition) */
782 Entry2 = DiskEntry->LogicalPartListHead.Flink;
783 while (Entry2 != &DiskEntry->LogicalPartListHead)
784 {
785 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
786 Entry2 = Entry2->Flink;
787
788 ASSERT(PartEntry->DiskEntry == DiskEntry);
789
790 DPRINT(" Logical Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
791 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
792 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
793 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
794 PartEntry->New ? "Yes" : "No",
795 PartEntry->AutoCreate ? "Yes" : "No",
796 PartEntry->FormatState,
797 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
798
799 if (ShouldICheckThisPartition(PartEntry))
800 FindNTOSInstallations(List, PartList, PartEntry);
801 }
802 }
803
804 #ifndef NDEBUG
805 /**** Debugging: List all the collected installations ****/
806 DumpNTOSInstalls(List);
807 #endif
808
809 return List;
810 }
811
812 /* EOF */