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