34c18e7b110c9a85442b90e5046765babfaa9de5
[reactos.git] / base / setup / usetup / 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 #include "usetup.h"
10
11 // HACK!
12 #include <strsafe.h>
13
14 #define NDEBUG
15 #include <debug.h>
16
17
18 /* GLOBALS ******************************************************************/
19
20 extern PPARTLIST PartitionList;
21
22 /* Language-independent Vendor strings */
23 static const PCWSTR KnownVendors[] = { L"ReactOS", L"Microsoft" };
24
25
26 /* FUNCTIONS ****************************************************************/
27
28 #if 0
29
30 BOOL IsWindowsOS(VOID)
31 {
32 // TODO:
33 // Load the "SystemRoot\System32\Config\SOFTWARE" hive and mount it,
34 // then go to (SOFTWARE\\)Microsoft\\Windows NT\\CurrentVersion,
35 // check the REG_SZ value "ProductName" and see whether it's "Windows"
36 // or "ReactOS". One may also check the REG_SZ "CurrentVersion" value,
37 // the REG_SZ "SystemRoot" and "PathName" values (what are the differences??).
38 //
39 // Optionally, looking at the SYSTEM hive, CurrentControlSet\\Control,
40 // REG_SZ values "SystemBootDevice" (and "FirmwareBootDevice" ??)...
41 //
42
43 /* ReactOS reports as Windows NT 5.2 */
44 HKEY hKey = NULL;
45
46 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
47 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
48 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
49 {
50 LONG ret;
51 DWORD dwType = 0, dwBufSize = 0;
52
53 ret = RegQueryValueExW(hKey, L"ProductName", NULL, &dwType, NULL, &dwBufSize);
54 if (ret == ERROR_SUCCESS && dwType == REG_SZ)
55 {
56 LPTSTR lpszProductName = (LPTSTR)MemAlloc(0, dwBufSize);
57 RegQueryValueExW(hKey, L"ProductName", NULL, &dwType, (LPBYTE)lpszProductName, &dwBufSize);
58
59 bIsWindowsOS = (FindSubStrI(lpszProductName, _T("Windows")) != NULL);
60
61 MemFree(lpszProductName);
62 }
63
64 RegCloseKey(hKey);
65 }
66
67 return bIsWindowsOS;
68 }
69
70 #endif
71
72 typedef enum _NTOS_BOOT_LOADER_TYPE
73 {
74 FreeLdr, // ReactOS' FreeLDR
75 NtLdr, // Windows <= 2k3 NTLDR
76 // BootMgr, // Vista+ BCD-oriented BOOTMGR
77 } NTOS_BOOT_LOADER_TYPE;
78
79 typedef struct _NTOS_BOOT_LOADER_FILES
80 {
81 NTOS_BOOT_LOADER_TYPE Type;
82 PCWSTR LoaderExecutable;
83 PCWSTR LoaderConfigurationFile;
84 // EnumerateInstallations;
85 } NTOS_BOOT_LOADER_FILES, *PNTOS_BOOT_LOADER_FILES;
86
87 // Question 1: What if config file is optional?
88 // Question 2: What if many config files are possible?
89 NTOS_BOOT_LOADER_FILES NtosBootLoaders[] =
90 {
91 {FreeLdr, L"freeldr.sys", L"freeldr.ini"},
92 {NtLdr , L"ntldr" , L"boot.ini"},
93 {NtLdr , L"setupldr" , L"txtsetup.sif"},
94 // {BootMgr, L"bootmgr" , ???}
95 };
96
97
98 static BOOLEAN
99 IsValidNTOSInstallation(
100 IN HANDLE SystemRootDirectory OPTIONAL,
101 IN PCWSTR SystemRoot OPTIONAL);
102
103 static PNTOS_INSTALLATION
104 AddNTOSInstallation(
105 IN PGENERIC_LIST List,
106 IN ULONG DiskNumber,
107 IN ULONG PartitionNumber,
108 IN PCWSTR SystemRoot,
109 IN PCWSTR InstallationName);
110
111 static NTSTATUS
112 FreeLdrEnumerateInstallations(
113 IN OUT PGENERIC_LIST List,
114 IN PPARTLIST PartList,
115 // IN PPARTENTRY PartEntry,
116 IN PCHAR FileBuffer,
117 IN ULONG FileLength)
118 {
119 NTSTATUS Status;
120 PINICACHE IniCache;
121 PINICACHEITERATOR Iterator;
122 PINICACHESECTION IniSection, OsIniSection;
123 PWCHAR SectionName, KeyData;
124 WCHAR InstallName[MAX_PATH];
125
126 /* Open an *existing* FreeLdr.ini configuration file */
127 Status = IniCacheLoadFromMemory(&IniCache, FileBuffer, FileLength, FALSE);
128 if (!NT_SUCCESS(Status))
129 return Status;
130
131 /* Get the "Operating Systems" section */
132 IniSection = IniCacheGetSection(IniCache, L"Operating Systems");
133 if (IniSection == NULL)
134 {
135 IniCacheDestroy(IniCache);
136 return STATUS_UNSUCCESSFUL;
137 }
138
139 /* Enumerate all the valid installations */
140 Iterator = IniCacheFindFirstValue(IniSection, &SectionName, &KeyData);
141 if (!Iterator) goto Quit;
142 do
143 {
144 // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni).
145 if (KeyData[0] == L'"')
146 {
147 /* Quoted name, copy up to the closing quote */
148 PWCHAR Begin = &KeyData[1];
149 PWCHAR End = wcschr(Begin, L'"');
150 if (!End)
151 End = Begin + wcslen(Begin);
152 StringCchCopyNW(InstallName, ARRAYSIZE(InstallName),
153 Begin, End - Begin);
154 }
155 else
156 {
157 /* Non-quoted name, copy everything */
158 StringCchCopyW(InstallName, ARRAYSIZE(InstallName), KeyData);
159 }
160
161 DPRINT1("Possible installation '%S' in OS section '%S'\n", InstallName, SectionName);
162
163 /* Search for an existing ReactOS entry */
164 OsIniSection = IniCacheGetSection(IniCache, SectionName);
165 if (!OsIniSection)
166 continue;
167
168 /* Check for supported boot type "Windows2003" */
169 Status = IniCacheGetKey(OsIniSection, L"BootType", &KeyData);
170 if (NT_SUCCESS(Status))
171 {
172 // TODO: What to do with "Windows" ; "WindowsNT40" ; "ReactOSSetup" ?
173 if ((KeyData == NULL) ||
174 ( (_wcsicmp(KeyData, L"Windows2003") != 0) &&
175 (_wcsicmp(KeyData, L"\"Windows2003\"") != 0) ))
176 {
177 /* This is not a ReactOS entry */
178 continue;
179 }
180 }
181 else
182 {
183 /* Certainly not a ReactOS installation */
184 continue;
185 }
186
187 /* BootType is Windows2003. Now check SystemPath. */
188 Status = IniCacheGetKey(OsIniSection, L"SystemPath", &KeyData);
189 if (!NT_SUCCESS(Status))
190 {
191 DPRINT1(" A Win2k3 install '%S' without an ARC path?!\n", InstallName);
192 continue;
193 }
194
195 {
196 HANDLE SystemRootDirectory;
197 OBJECT_ATTRIBUTES ObjectAttributes;
198 IO_STATUS_BLOCK IoStatusBlock;
199 UNICODE_STRING SystemRootPath;
200 WCHAR SystemRoot[MAX_PATH];
201 WCHAR InstallNameW[MAX_PATH];
202
203 DPRINT1(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n", InstallName, KeyData);
204
205 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
206 // should be done.
207
208 // TODO 1: Normalize the ARC path.
209
210 // TODO 2: Check whether we already have an installation with this ARC path.
211 // If that's the case, stop there. If not, continue...
212
213 /*
214 * Convert the ARC path into an NT path, from which we will deduce
215 * the real disk drive & partition on which the candidate installation
216 * resides, as well verifying whether it is indeed an NTOS installation.
217 */
218 RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot));
219 if (!ArcPathToNtPath(&SystemRootPath, KeyData, PartList))
220 {
221 DPRINT1("ArcPathToNtPath(%S) failed, installation skipped.\n", KeyData);
222 // FIXME: Do not continue!
223 continue;
224 }
225
226 DPRINT1("ArcPathToNtPath() succeeded: %S --> %wZ\n", KeyData, &SystemRootPath);
227
228 // TODO 3: Check whether we already have an installation with this NT path.
229 // If that's the case, stop there. If not, continue...
230
231 /* Set SystemRootPath */
232 DPRINT1("FreeLdrEnumerateInstallations: SystemRootPath: %wZ\n", &SystemRootPath);
233
234 /* Open SystemRootPath */
235 InitializeObjectAttributes(&ObjectAttributes,
236 &SystemRootPath,
237 OBJ_CASE_INSENSITIVE,
238 NULL,
239 NULL);
240 Status = NtOpenFile(&SystemRootDirectory,
241 FILE_LIST_DIRECTORY | SYNCHRONIZE,
242 &ObjectAttributes,
243 &IoStatusBlock,
244 FILE_SHARE_READ | FILE_SHARE_WRITE,
245 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
246 if (!NT_SUCCESS(Status))
247 {
248 DPRINT1("Failed to open SystemRoot %wZ, Status 0x%08lx\n", &SystemRootPath, Status);
249 continue;
250 }
251
252 if (IsValidNTOSInstallation(SystemRootDirectory, NULL))
253 {
254 ULONG DiskNumber, PartitionNumber;
255 PCWSTR PathComponent;
256 PDISKENTRY DiskEntry = NULL;
257 PPARTENTRY PartEntry = NULL;
258
259 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path %S, NT path %wZ\n", KeyData, &SystemRootPath);
260
261 /* From the NT path, compute the disk, partition and path components */
262 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
263 {
264 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
265 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
266
267 /* Retrieve the corresponding disk and partition */
268 if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
269 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber);
270 }
271 else
272 {
273 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
274 }
275
276 if (PartEntry && PartEntry->DriveLetter)
277 {
278 /* We have retrieved a partition that is mounted */
279 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%s\"",
280 PartEntry->DriveLetter, PathComponent, InstallName);
281 }
282 else
283 {
284 /* We failed somewhere, just show the NT path */
285 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%s\"",
286 &SystemRootPath, InstallName);
287 }
288 AddNTOSInstallation(List, 0, 0 /*DiskNumber, PartitionNumber*/, KeyData, InstallNameW);
289 }
290
291 NtClose(SystemRootDirectory);
292 }
293 }
294 while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData));
295
296 IniCacheFindClose(Iterator);
297
298 Quit:
299 IniCacheDestroy(IniCache);
300 return STATUS_SUCCESS;
301 }
302
303 static NTSTATUS
304 NtLdrEnumerateInstallations(
305 IN OUT PGENERIC_LIST List,
306 IN PPARTLIST PartList,
307 // IN PPARTENTRY PartEntry,
308 IN PCHAR FileBuffer,
309 IN ULONG FileLength)
310 {
311 NTSTATUS Status;
312 PINICACHE IniCache;
313 PINICACHEITERATOR Iterator;
314 PINICACHESECTION IniSection;
315 PWCHAR SectionName, KeyData;
316 WCHAR InstallName[MAX_PATH];
317
318 /* Open an *existing* FreeLdr.ini configuration file */
319 Status = IniCacheLoadFromMemory(&IniCache, FileBuffer, FileLength, FALSE);
320 if (!NT_SUCCESS(Status))
321 return Status;
322
323 /* Get the "Operating Systems" section */
324 IniSection = IniCacheGetSection(IniCache, L"operating systems");
325 if (IniSection == NULL)
326 {
327 IniCacheDestroy(IniCache);
328 return STATUS_UNSUCCESSFUL;
329 }
330
331 /* Enumerate all the valid installations */
332 Iterator = IniCacheFindFirstValue(IniSection, &SectionName, &KeyData);
333 if (!Iterator) goto Quit;
334 do
335 {
336 // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni).
337 if (KeyData[0] == L'"')
338 {
339 /* Quoted name, copy up to the closing quote */
340 PWCHAR Begin = &KeyData[1];
341 PWCHAR End = wcschr(Begin, L'"');
342 if (!End)
343 End = Begin + wcslen(Begin);
344 StringCchCopyNW(InstallName, ARRAYSIZE(InstallName),
345 Begin, End - Begin);
346 }
347 else
348 {
349 /* Non-quoted name, copy everything */
350 StringCchCopyW(InstallName, ARRAYSIZE(InstallName), KeyData);
351 }
352
353 DPRINT1("Possible installation '%S' with ARC path '%S'\n", InstallName, SectionName);
354
355 // FIXME TODO: Determine whether we indeed have an ARC path, in which case
356 // this is an NT installation, or, whether we have something else like a DOS
357 // path, which means that we are booting a boot sector...
358
359 DPRINT1(" Found a Win2k3 install '%S' with ARC path '%S'\n", InstallName, SectionName);
360 // TODO: Dissect it in order to retrieve the real disk drive & partition numbers.
361 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
362 // should be done.
363 {
364 HANDLE SystemRootDirectory;
365 OBJECT_ATTRIBUTES ObjectAttributes;
366 IO_STATUS_BLOCK IoStatusBlock;
367 UNICODE_STRING SystemRootPath;
368 WCHAR SystemRoot[MAX_PATH];
369 WCHAR InstallNameW[MAX_PATH];
370
371 // Note that in ARC path, the disk number is the BIOS disk number, so a conversion
372 // should be done.
373
374 // TODO 1: Normalize the ARC path.
375
376 // TODO 2: Check whether we already have an installation with this ARC path.
377 // If that's the case, stop there. If not, continue...
378
379 /*
380 * Convert the ARC path into an NT path, from which we will deduce
381 * the real disk drive & partition on which the candidate installation
382 * resides, as well verifying whether it is indeed an NTOS installation.
383 */
384 RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot));
385 if (!ArcPathToNtPath(&SystemRootPath, SectionName, PartList))
386 {
387 DPRINT1("ArcPathToNtPath(%S) failed, installation skipped.\n", SectionName);
388 // FIXME: Do not continue!
389 continue;
390 }
391
392 DPRINT1("ArcPathToNtPath() succeeded: %S --> %wZ\n", SectionName, &SystemRootPath);
393
394 // TODO 3: Check whether we already have an installation with this NT path.
395 // If that's the case, stop there. If not, continue...
396
397 /* Set SystemRootPath */
398 DPRINT1("NtLdrEnumerateInstallations: SystemRootPath: %wZ\n", &SystemRootPath);
399
400 /* Open SystemRootPath */
401 InitializeObjectAttributes(&ObjectAttributes,
402 &SystemRootPath,
403 OBJ_CASE_INSENSITIVE,
404 NULL,
405 NULL);
406 Status = NtOpenFile(&SystemRootDirectory,
407 FILE_LIST_DIRECTORY | SYNCHRONIZE,
408 &ObjectAttributes,
409 &IoStatusBlock,
410 FILE_SHARE_READ | FILE_SHARE_WRITE,
411 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
412 if (!NT_SUCCESS(Status))
413 {
414 DPRINT1("Failed to open SystemRoot %wZ, Status 0x%08lx\n", &SystemRootPath, Status);
415 continue;
416 }
417
418 if (IsValidNTOSInstallation(SystemRootDirectory, NULL))
419 {
420 ULONG DiskNumber, PartitionNumber;
421 PCWSTR PathComponent;
422 PDISKENTRY DiskEntry = NULL;
423 PPARTENTRY PartEntry = NULL;
424
425 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path %S, NT path %wZ\n", SectionName, &SystemRootPath);
426
427 /* From the NT path, compute the disk, partition and path components */
428 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
429 {
430 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
431 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
432
433 /* Retrieve the corresponding disk and partition */
434 if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
435 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber);
436 }
437 else
438 {
439 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
440 }
441
442 if (PartEntry && PartEntry->DriveLetter)
443 {
444 /* We have retrieved a partition that is mounted */
445 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%s\"",
446 PartEntry->DriveLetter, PathComponent, InstallName);
447 }
448 else
449 {
450 /* We failed somewhere, just show the NT path */
451 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%s\"",
452 &SystemRootPath, InstallName);
453 }
454 AddNTOSInstallation(List, 0, 0 /*DiskNumber, PartitionNumber*/, SectionName, InstallNameW);
455 }
456
457 NtClose(SystemRootDirectory);
458 }
459 }
460 while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData));
461
462 IniCacheFindClose(Iterator);
463
464 Quit:
465 IniCacheDestroy(IniCache);
466 return STATUS_SUCCESS;
467 }
468
469 /*
470 * FindSubStrI(PCWSTR str, PCWSTR strSearch) :
471 * Searches for a sub-string 'strSearch' inside 'str', similarly to what
472 * wcsstr(str, strSearch) does, but ignores the case during the comparisons.
473 */
474 PWSTR FindSubStrI(PCWSTR str, PCWSTR strSearch)
475 {
476 PWSTR cp = (PWSTR)str;
477 PWSTR s1, s2;
478
479 if (!*strSearch)
480 return (PWSTR)str;
481
482 while (*cp)
483 {
484 s1 = cp;
485 s2 = (PWSTR)strSearch;
486
487 while (*s1 && *s2 && (towupper(*s1) == towupper(*s2)))
488 ++s1, ++s2;
489
490 if (!*s2)
491 return cp;
492
493 ++cp;
494 }
495
496 return NULL;
497 }
498
499 static BOOLEAN
500 CheckForValidPEAndVendor(
501 IN HANDLE RootDirectory OPTIONAL,
502 IN PCWSTR PathName OPTIONAL,
503 IN PCWSTR FileName, // OPTIONAL
504 OUT PUNICODE_STRING VendorName
505 )
506 {
507 BOOLEAN Success = FALSE;
508 NTSTATUS Status;
509 HANDLE FileHandle, SectionHandle;
510 // SIZE_T ViewSize;
511 PVOID ViewBase;
512 PVOID VersionBuffer = NULL; // Read-only
513 PVOID pvData = NULL;
514 UINT BufLen = 0;
515
516 if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
517 return FALSE;
518
519 *VendorName->Buffer = UNICODE_NULL;
520 VendorName->Length = 0;
521
522 Status = OpenAndMapFile(RootDirectory, PathName, FileName,
523 &FileHandle, &SectionHandle, &ViewBase, NULL);
524 if (!NT_SUCCESS(Status))
525 {
526 DPRINT1("Failed to open and map file %S, Status 0x%08lx\n", FileName, Status);
527 return FALSE; // Status;
528 }
529
530 /* Make sure it's a valid PE file */
531 if (!RtlImageNtHeader(ViewBase))
532 {
533 DPRINT1("File %S does not seem to be a valid PE, bail out\n", FileName);
534 Status = STATUS_INVALID_IMAGE_FORMAT;
535 goto UnmapFile;
536 }
537
538 /*
539 * Search for a valid executable version and vendor.
540 * NOTE: The module is loaded as a data file, it should be marked as such.
541 */
542 Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL);
543 if (!NT_SUCCESS(Status))
544 {
545 DPRINT1("Failed to get version resource for file %S, Status 0x%08lx\n", FileName, Status);
546 goto UnmapFile;
547 }
548
549 Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen);
550 if (NT_SUCCESS(Status))
551 {
552 USHORT wCodePage = 0, wLangID = 0;
553 WCHAR FileInfo[MAX_PATH];
554
555 wCodePage = LOWORD(*(ULONG*)pvData);
556 wLangID = HIWORD(*(ULONG*)pvData);
557
558 StringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo),
559 L"StringFileInfo\\%04X%04X\\CompanyName",
560 wCodePage, wLangID);
561
562 Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen);
563
564 /* Fixup the Status in case pvData is NULL */
565 if (NT_SUCCESS(Status) && !pvData)
566 Status = STATUS_NOT_FOUND;
567
568 if (NT_SUCCESS(Status) /*&& pvData*/)
569 {
570 /* BufLen includes the NULL terminator count */
571 DPRINT1("Found version vendor: \"%S\" for file %S\n", pvData, FileName);
572
573 StringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength,
574 pvData, BufLen * sizeof(WCHAR));
575 VendorName->Length = wcslen(VendorName->Buffer) * sizeof(WCHAR);
576
577 Success = TRUE;
578 }
579 }
580
581 if (!NT_SUCCESS(Status))
582 DPRINT1("No version vendor found for file %S\n", FileName);
583
584 UnmapFile:
585 /* Finally, unmap and close the file */
586 UnMapFile(SectionHandle, ViewBase);
587 NtClose(FileHandle);
588
589 return Success;
590 }
591
592 //
593 // TODO: Instead of returning TRUE/FALSE, it would be nice to return
594 // a flag indicating:
595 // - whether the installation is actually valid;
596 // - if it's broken or not (aka. needs for repair, or just upgrading).
597 //
598 static BOOLEAN
599 IsValidNTOSInstallation(
600 IN HANDLE SystemRootDirectory OPTIONAL,
601 IN PCWSTR SystemRoot OPTIONAL)
602 {
603 BOOLEAN Success = FALSE;
604 USHORT i;
605 WCHAR PathBuffer[MAX_PATH];
606 UNICODE_STRING VendorName;
607
608 /*
609 * Use either the 'SystemRootDirectory' handle or the 'SystemRoot' string,
610 * depending on what the user gave to us in entry.
611 */
612 if (SystemRootDirectory)
613 SystemRoot = NULL;
614 // else SystemRootDirectory == NULL and SystemRoot is what it is.
615
616 /* If both the parameters are NULL we cannot do anything else more */
617 if (!SystemRootDirectory && !SystemRoot)
618 return FALSE;
619
620 // DoesPathExist(SystemRootDirectory, SystemRoot, L"System32\\"); etc...
621
622 /* Check for the existence of \SystemRoot\System32 */
623 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\");
624 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
625 {
626 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
627 return FALSE;
628 }
629
630 /* Check for the existence of \SystemRoot\System32\drivers */
631 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\drivers\\");
632 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
633 {
634 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
635 return FALSE;
636 }
637
638 /* Check for the existence of \SystemRoot\System32\config */
639 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\config\\");
640 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
641 {
642 // DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &FileName, Status);
643 return FALSE;
644 }
645
646 #if 0
647 /*
648 * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config
649 * (but we don't check here whether they are actually valid).
650 */
651 if (!DoesFileExist(SystemRootDirectory, SystemRoot, L"System32\\config\\SYSTEM"))
652 {
653 // DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &FileName, Status);
654 return FALSE;
655 }
656 if (!DoesFileExist(SystemRootDirectory, SystemRoot, L"System32\\config\\SOFTWARE"))
657 {
658 // DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &FileName, Status);
659 return FALSE;
660 }
661 #endif
662
663 RtlInitEmptyUnicodeString(&VendorName, PathBuffer, sizeof(PathBuffer));
664
665 /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */
666 Success = CheckForValidPEAndVendor(SystemRootDirectory, SystemRoot, L"System32\\ntoskrnl.exe", &VendorName);
667 if (!Success)
668 DPRINT1("Kernel file ntoskrnl.exe is either not a PE file, or does not have any vendor?\n");
669
670 /* The kernel gives the OS its flavour */
671 if (Success)
672 {
673 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
674 {
675 Success = !!FindSubStrI(VendorName.Buffer, KnownVendors[i]);
676 if (Success)
677 {
678 /* We have found a correct vendor combination */
679 DPRINT1("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]);
680 break;
681 }
682 }
683 }
684
685 /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */
686
687 /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */
688 Success = CheckForValidPEAndVendor(SystemRootDirectory, SystemRoot, L"System32\\ntdll.dll", &VendorName);
689 if (!Success)
690 DPRINT1("User-mode file ntdll.dll is either not a PE file, or does not have any vendor?\n");
691 if (Success)
692 {
693 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
694 {
695 if (!!FindSubStrI(VendorName.Buffer, KnownVendors[i]))
696 {
697 /* We have found a correct vendor combination */
698 DPRINT1("IsValidNTOSInstallation: The user-mode file ntdll.dll is from %S\n", KnownVendors[i]);
699 break;
700 }
701 }
702 }
703
704 return Success;
705 }
706
707 static VOID
708 DumpNTOSInstalls(
709 IN PGENERIC_LIST List)
710 {
711 PGENERIC_LIST_ENTRY Entry;
712 PNTOS_INSTALLATION NtOsInstall;
713 ULONG NtOsInstallsCount = GetNumberOfListEntries(List);
714
715 DPRINT1("There %s %d installation%s detected:\n",
716 NtOsInstallsCount >= 2 ? "are" : "is",
717 NtOsInstallsCount,
718 NtOsInstallsCount >= 2 ? "s" : "");
719
720 Entry = GetFirstListEntry(List);
721 while (Entry)
722 {
723 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry);
724 Entry = GetNextListEntry(Entry);
725
726 DPRINT1(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot %S\n",
727 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber,
728 NtOsInstall->InstallationName, NtOsInstall->SystemRoot);
729 }
730
731 DPRINT1("Done.\n");
732 }
733
734 static PNTOS_INSTALLATION
735 FindExistingNTOSInstall(
736 IN PGENERIC_LIST List,
737 IN ULONG DiskNumber,
738 IN ULONG PartitionNumber,
739 IN PCWSTR SystemRoot)
740 {
741 PGENERIC_LIST_ENTRY Entry;
742 PNTOS_INSTALLATION NtOsInstall;
743
744 Entry = GetFirstListEntry(List);
745 while (Entry)
746 {
747 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry);
748 Entry = GetNextListEntry(Entry);
749
750 if (NtOsInstall->DiskNumber == DiskNumber &&
751 NtOsInstall->PartitionNumber == PartitionNumber &&
752 _wcsicmp(NtOsInstall->SystemRoot, SystemRoot) == 0)
753 {
754 /* Found it! */
755 return NtOsInstall;
756 }
757 }
758
759 return NULL;
760 }
761
762 static PNTOS_INSTALLATION
763 AddNTOSInstallation(
764 IN PGENERIC_LIST List,
765 IN ULONG DiskNumber,
766 IN ULONG PartitionNumber,
767 IN PCWSTR SystemRoot,
768 IN PCWSTR InstallationName)
769 {
770 PNTOS_INSTALLATION NtOsInstall;
771 CHAR InstallNameA[MAX_PATH];
772
773 /* Is there already any installation with these settings? */
774 NtOsInstall = FindExistingNTOSInstall(List, DiskNumber, PartitionNumber, SystemRoot);
775 if (NtOsInstall)
776 {
777 DPRINT1("An NTOS installation with name \"%S\" already exists on disk #%d, partition #%d, in SystemRoot %S\n",
778 NtOsInstall->InstallationName, NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, NtOsInstall->SystemRoot);
779 //
780 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
781 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
782 //
783 return NtOsInstall;
784 }
785
786 /* None was found, so add a new one */
787 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, sizeof(*NtOsInstall));
788 if (!NtOsInstall)
789 return NULL;
790
791 NtOsInstall->DiskNumber = DiskNumber;
792 NtOsInstall->PartitionNumber = PartitionNumber;
793 StringCchCopyW(NtOsInstall->SystemRoot, ARRAYSIZE(NtOsInstall->SystemRoot), SystemRoot);
794 StringCchCopyW(NtOsInstall->InstallationName, ARRAYSIZE(NtOsInstall->InstallationName), InstallationName);
795
796 // Having the GENERIC_LIST storing the display item string plainly sucks...
797 StringCchPrintfA(InstallNameA, ARRAYSIZE(InstallNameA), "%S", InstallationName);
798 AppendGenericListEntry(List, InstallNameA, NtOsInstall, FALSE);
799
800 return NtOsInstall;
801 }
802
803 static VOID
804 FindNTOSInstallations(
805 IN OUT PGENERIC_LIST List,
806 IN PPARTLIST PartList,
807 IN PPARTENTRY PartEntry)
808 {
809 NTSTATUS Status;
810 UINT i;
811 HANDLE PartitionHandle, FileHandle;
812 OBJECT_ATTRIBUTES ObjectAttributes;
813 IO_STATUS_BLOCK IoStatusBlock;
814 UNICODE_STRING PartitionRootPath;
815 HANDLE SectionHandle;
816 // SIZE_T ViewSize;
817 ULONG FileSize;
818 PVOID ViewBase;
819 WCHAR PathBuffer[MAX_PATH];
820
821 PDISKENTRY DiskEntry = PartEntry->DiskEntry;
822 ULONG DiskNumber = DiskEntry->DiskNumber;
823 ULONG PartitionNumber = PartEntry->PartitionNumber;
824
825 /* Set PartitionRootPath */
826 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer),
827 L"\\Device\\Harddisk%lu\\Partition%lu\\",
828 DiskNumber, PartitionNumber);
829 RtlInitUnicodeString(&PartitionRootPath, PathBuffer);
830 DPRINT1("FindNTOSInstallations: PartitionRootPath: %wZ\n", &PartitionRootPath);
831
832 /* Open the partition */
833 InitializeObjectAttributes(&ObjectAttributes,
834 &PartitionRootPath,
835 OBJ_CASE_INSENSITIVE,
836 NULL,
837 NULL);
838 Status = NtOpenFile(&PartitionHandle,
839 FILE_LIST_DIRECTORY | SYNCHRONIZE,
840 &ObjectAttributes,
841 &IoStatusBlock,
842 FILE_SHARE_READ | FILE_SHARE_WRITE,
843 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
844 if (!NT_SUCCESS(Status))
845 {
846 DPRINT1("Failed to open partition %wZ, Status 0x%08lx\n", &PartitionRootPath, Status);
847 return;
848 }
849
850 /* Try to see whether we recognize some NT boot loaders */
851 for (i = 0; i < ARRAYSIZE(NtosBootLoaders); ++i)
852 {
853 /* Check whether the loader executable exists */
854 if (!DoesFileExist(PartitionHandle, NULL, NtosBootLoaders[i].LoaderExecutable))
855 {
856 /* The loader does not exist, continue with another one */
857 DPRINT1("Loader executable %S does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderExecutable);
858 continue;
859 }
860
861 /* Check whether the loader configuration file exists */
862 Status = OpenAndMapFile(PartitionHandle, NULL, NtosBootLoaders[i].LoaderConfigurationFile,
863 &FileHandle, &SectionHandle, &ViewBase, &FileSize);
864 if (!NT_SUCCESS(Status))
865 {
866 /* The loader does not exist, continue with another one */
867 // FIXME: Consider it might be optional??
868 DPRINT1("Loader configuration file %S does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderConfigurationFile);
869 continue;
870 }
871
872 /* The loader configuration file exists, interpret it to find valid installations */
873 DPRINT1("Analyse the OS installations inside '%S' in disk #%d, partition #%d\n",
874 NtosBootLoaders[i].LoaderConfigurationFile, DiskNumber, PartitionNumber);
875 switch (NtosBootLoaders[i].Type)
876 {
877 case FreeLdr:
878 Status = FreeLdrEnumerateInstallations(List, PartList, ViewBase, FileSize);
879 break;
880
881 case NtLdr:
882 Status = NtLdrEnumerateInstallations(List, PartList, ViewBase, FileSize);
883 break;
884
885 default:
886 DPRINT1("Loader type %d is currently unsupported!\n", NtosBootLoaders[i].Type);
887 Status = STATUS_SUCCESS;
888 }
889
890 /* Finally, unmap and close the file */
891 UnMapFile(SectionHandle, ViewBase);
892 NtClose(FileHandle);
893 }
894
895 /* Close the partition */
896 NtClose(PartitionHandle);
897 }
898
899 // static
900 FORCEINLINE BOOLEAN
901 ShouldICheckThisPartition(
902 IN PPARTENTRY PartEntry)
903 {
904 if (!PartEntry)
905 return FALSE;
906
907 return PartEntry->IsPartitioned &&
908 !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
909 !PartEntry->New &&
910 (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */);
911 }
912
913 // EnumerateNTOSInstallations
914 PGENERIC_LIST
915 CreateNTOSInstallationsList(
916 IN PPARTLIST PartList)
917 {
918 PGENERIC_LIST List;
919 PLIST_ENTRY Entry, Entry2;
920 PDISKENTRY DiskEntry;
921 PPARTENTRY PartEntry;
922
923 List = CreateGenericList();
924 if (List == NULL)
925 return NULL;
926
927 /* Loop each available disk ... */
928 Entry = PartList->DiskListHead.Flink;
929 while (Entry != &PartList->DiskListHead)
930 {
931 DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
932 Entry = Entry->Flink;
933
934 DPRINT1("Disk #%d\n", DiskEntry->DiskNumber);
935
936 /* ... and for each disk, loop each available partition */
937
938 /* First, the primary partitions */
939 Entry2 = DiskEntry->PrimaryPartListHead.Flink;
940 while (Entry2 != &DiskEntry->PrimaryPartListHead)
941 {
942 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
943 Entry2 = Entry2->Flink;
944
945 ASSERT(PartEntry->DiskEntry == DiskEntry);
946
947 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",
948 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
949 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
950 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
951 PartEntry->New ? "Yes" : "No",
952 PartEntry->AutoCreate ? "Yes" : "No",
953 PartEntry->FormatState,
954 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
955
956 if (ShouldICheckThisPartition(PartEntry))
957 FindNTOSInstallations(List, PartList, PartEntry);
958 }
959
960 /* Then, the logical partitions (present in the extended partition) */
961 Entry2 = DiskEntry->LogicalPartListHead.Flink;
962 while (Entry2 != &DiskEntry->LogicalPartListHead)
963 {
964 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
965 Entry2 = Entry2->Flink;
966
967 ASSERT(PartEntry->DiskEntry == DiskEntry);
968
969 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",
970 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
971 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
972 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
973 PartEntry->New ? "Yes" : "No",
974 PartEntry->AutoCreate ? "Yes" : "No",
975 PartEntry->FormatState,
976 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
977
978 if (ShouldICheckThisPartition(PartEntry))
979 FindNTOSInstallations(List, PartList, PartEntry);
980 }
981 }
982
983 /**** Debugging: List all the collected installations ****/
984 DumpNTOSInstalls(List);
985
986 return List;
987 }
988
989 /* EOF */