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