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