a695da7466a361576a797043a695e2f403bb7238
[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(
109 IN HANDLE SystemRootDirectory OPTIONAL,
110 IN PCWSTR SystemRoot OPTIONAL);
111
112 static PNTOS_INSTALLATION
113 FindExistingNTOSInstall(
114 IN PGENERIC_LIST List,
115 IN PCWSTR SystemRootArcPath OPTIONAL,
116 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
117 );
118
119 static PNTOS_INSTALLATION
120 AddNTOSInstallation(
121 IN PGENERIC_LIST List,
122 IN PCWSTR SystemRootArcPath,
123 IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
124 IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer
125 IN ULONG DiskNumber,
126 IN ULONG PartitionNumber,
127 IN PPARTENTRY PartEntry OPTIONAL,
128 IN PCWSTR InstallationName);
129
130 static NTSTATUS
131 FreeLdrEnumerateInstallations(
132 IN OUT PGENERIC_LIST List,
133 IN PPARTLIST PartList,
134 // IN PPARTENTRY PartEntry,
135 IN PCHAR FileBuffer,
136 IN ULONG FileLength)
137 {
138 NTSTATUS Status;
139 PINICACHE IniCache;
140 PINICACHEITERATOR Iterator;
141 PINICACHESECTION IniSection, OsIniSection;
142 PWCHAR SectionName, KeyData;
143 UNICODE_STRING InstallName;
144
145 HANDLE SystemRootDirectory;
146 OBJECT_ATTRIBUTES ObjectAttributes;
147 IO_STATUS_BLOCK IoStatusBlock;
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 /* Open SystemRootPath */
268 InitializeObjectAttributes(&ObjectAttributes,
269 &SystemRootPath,
270 OBJ_CASE_INSENSITIVE,
271 NULL,
272 NULL);
273 Status = NtOpenFile(&SystemRootDirectory,
274 FILE_LIST_DIRECTORY | SYNCHRONIZE,
275 &ObjectAttributes,
276 &IoStatusBlock,
277 FILE_SHARE_READ | FILE_SHARE_WRITE,
278 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
279 if (!NT_SUCCESS(Status))
280 {
281 DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", &SystemRootPath, Status);
282 continue;
283 }
284
285 if (IsValidNTOSInstallation(SystemRootDirectory, NULL))
286 {
287 ULONG DiskNumber = 0, PartitionNumber = 0;
288 PCWSTR PathComponent = NULL;
289 PDISKENTRY DiskEntry = NULL;
290 PPARTENTRY PartEntry = NULL;
291
292 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", KeyData, &SystemRootPath);
293
294 /* From the NT path, compute the disk, partition and path components */
295 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
296 {
297 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
298 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
299
300 /* Retrieve the corresponding disk and partition */
301 if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
302 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber);
303 }
304 else
305 {
306 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
307 }
308
309 if (PartEntry && PartEntry->DriveLetter)
310 {
311 /* We have retrieved a partition that is mounted */
312 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%wZ\"",
313 PartEntry->DriveLetter, PathComponent, &InstallName);
314 }
315 else
316 {
317 /* We failed somewhere, just show the NT path */
318 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%wZ\"",
319 &SystemRootPath, &InstallName);
320 }
321 AddNTOSInstallation(List, KeyData, &SystemRootPath, PathComponent,
322 DiskNumber, PartitionNumber, PartEntry,
323 InstallNameW);
324 }
325
326 NtClose(SystemRootDirectory);
327 }
328 while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData));
329
330 IniCacheFindClose(Iterator);
331
332 Quit:
333 IniCacheDestroy(IniCache);
334 return STATUS_SUCCESS;
335 }
336
337 static NTSTATUS
338 NtLdrEnumerateInstallations(
339 IN OUT PGENERIC_LIST List,
340 IN PPARTLIST PartList,
341 // IN PPARTENTRY PartEntry,
342 IN PCHAR FileBuffer,
343 IN ULONG FileLength)
344 {
345 NTSTATUS Status;
346 PINICACHE IniCache;
347 PINICACHEITERATOR Iterator;
348 PINICACHESECTION IniSection;
349 PWCHAR SectionName, KeyData;
350 UNICODE_STRING InstallName;
351
352 HANDLE SystemRootDirectory;
353 OBJECT_ATTRIBUTES ObjectAttributes;
354 IO_STATUS_BLOCK IoStatusBlock;
355 PNTOS_INSTALLATION NtOsInstall;
356 UNICODE_STRING SystemRootPath;
357 WCHAR SystemRoot[MAX_PATH];
358 WCHAR InstallNameW[MAX_PATH];
359
360 /* Open an *existing* FreeLdr.ini configuration file */
361 Status = IniCacheLoadFromMemory(&IniCache, FileBuffer, FileLength, FALSE);
362 if (!NT_SUCCESS(Status))
363 return Status;
364
365 /* Get the "Operating Systems" section */
366 IniSection = IniCacheGetSection(IniCache, L"operating systems");
367 if (IniSection == NULL)
368 {
369 IniCacheDestroy(IniCache);
370 return STATUS_UNSUCCESSFUL;
371 }
372
373 /* Enumerate all the valid installations */
374 Iterator = IniCacheFindFirstValue(IniSection, &SectionName, &KeyData);
375 if (!Iterator) goto Quit;
376 do
377 {
378 // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni).
379 if (KeyData[0] == L'"')
380 {
381 /* Quoted name, copy up to the closing quote */
382 PWCHAR Begin = &KeyData[1];
383 PWCHAR End = wcschr(Begin, L'"');
384 if (!End)
385 End = Begin + wcslen(Begin);
386 RtlInitEmptyUnicodeString(&InstallName, Begin, (ULONG_PTR)End - (ULONG_PTR)Begin);
387 InstallName.Length = InstallName.MaximumLength;
388 }
389 else
390 {
391 /* Non-quoted name, copy everything */
392 RtlInitUnicodeString(&InstallName, KeyData);
393 }
394
395 DPRINT1("Possible installation '%wZ' with ARC path '%S'\n", &InstallName, SectionName);
396
397 DPRINT1(" Found a Win2k3 install '%wZ' with ARC path '%S'\n", &InstallName, SectionName);
398
399 // TODO: Normalize the ARC path.
400
401 /*
402 * Check whether we already have an installation with this ARC path.
403 * If this is the case, stop there.
404 */
405 NtOsInstall = FindExistingNTOSInstall(List, SectionName, NULL);
406 if (NtOsInstall)
407 {
408 DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n",
409 NtOsInstall->InstallationName, &NtOsInstall->SystemArcPath);
410 continue;
411 }
412
413 /*
414 * Convert the ARC path into an NT path, from which we will deduce
415 * the real disk drive & partition on which the candidate installation
416 * resides, as well verifying whether it is indeed an NTOS installation.
417 */
418 RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot));
419 if (!ArcPathToNtPath(&SystemRootPath, SectionName, PartList))
420 {
421 DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", SectionName);
422 continue;
423 }
424
425 DPRINT1("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n", SectionName, &SystemRootPath);
426
427 /*
428 * Check whether we already have an installation with this NT path.
429 * If this is the case, stop there.
430 */
431 NtOsInstall = FindExistingNTOSInstall(List, NULL /*SectionName*/, &SystemRootPath);
432 if (NtOsInstall)
433 {
434 DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n",
435 NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath);
436 continue;
437 }
438
439 /* Set SystemRootPath */
440 DPRINT1("NtLdrEnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath);
441
442 /* Open SystemRootPath */
443 InitializeObjectAttributes(&ObjectAttributes,
444 &SystemRootPath,
445 OBJ_CASE_INSENSITIVE,
446 NULL,
447 NULL);
448 Status = NtOpenFile(&SystemRootDirectory,
449 FILE_LIST_DIRECTORY | SYNCHRONIZE,
450 &ObjectAttributes,
451 &IoStatusBlock,
452 FILE_SHARE_READ | FILE_SHARE_WRITE,
453 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
454 if (!NT_SUCCESS(Status))
455 {
456 DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", &SystemRootPath, Status);
457 continue;
458 }
459
460 if (IsValidNTOSInstallation(SystemRootDirectory, NULL))
461 {
462 ULONG DiskNumber = 0, PartitionNumber = 0;
463 PCWSTR PathComponent = NULL;
464 PDISKENTRY DiskEntry = NULL;
465 PPARTENTRY PartEntry = NULL;
466
467 DPRINT1("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", SectionName, &SystemRootPath);
468
469 /* From the NT path, compute the disk, partition and path components */
470 if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
471 {
472 DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
473 &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
474
475 /* Retrieve the corresponding disk and partition */
476 if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
477 DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber);
478 }
479 else
480 {
481 DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
482 }
483
484 if (PartEntry && PartEntry->DriveLetter)
485 {
486 /* We have retrieved a partition that is mounted */
487 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%wZ\"",
488 PartEntry->DriveLetter, PathComponent, &InstallName);
489 }
490 else
491 {
492 /* We failed somewhere, just show the NT path */
493 StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%wZ\"",
494 &SystemRootPath, &InstallName);
495 }
496 AddNTOSInstallation(List, SectionName, &SystemRootPath, PathComponent,
497 DiskNumber, PartitionNumber, PartEntry,
498 InstallNameW);
499 }
500
501 NtClose(SystemRootDirectory);
502 }
503 while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData));
504
505 IniCacheFindClose(Iterator);
506
507 Quit:
508 IniCacheDestroy(IniCache);
509 return STATUS_SUCCESS;
510 }
511
512 /*
513 * FindSubStrI(PCWSTR str, PCWSTR strSearch) :
514 * Searches for a sub-string 'strSearch' inside 'str', similarly to what
515 * wcsstr(str, strSearch) does, but ignores the case during the comparisons.
516 */
517 PCWSTR FindSubStrI(PCWSTR str, PCWSTR strSearch)
518 {
519 PCWSTR cp = str;
520 PCWSTR s1, s2;
521
522 if (!*strSearch)
523 return str;
524
525 while (*cp)
526 {
527 s1 = cp;
528 s2 = strSearch;
529
530 while (*s1 && *s2 && (towupper(*s1) == towupper(*s2)))
531 ++s1, ++s2;
532
533 if (!*s2)
534 return cp;
535
536 ++cp;
537 }
538
539 return NULL;
540 }
541
542 static BOOLEAN
543 CheckForValidPEAndVendor(
544 IN HANDLE RootDirectory OPTIONAL,
545 IN PCWSTR PathName OPTIONAL,
546 IN PCWSTR FileName, // OPTIONAL
547 OUT PUNICODE_STRING VendorName
548 )
549 {
550 BOOLEAN Success = FALSE;
551 NTSTATUS Status;
552 HANDLE FileHandle, SectionHandle;
553 // SIZE_T ViewSize;
554 PVOID ViewBase;
555 PVOID VersionBuffer = NULL; // Read-only
556 PVOID pvData = NULL;
557 UINT BufLen = 0;
558
559 if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
560 return FALSE;
561
562 *VendorName->Buffer = UNICODE_NULL;
563 VendorName->Length = 0;
564
565 Status = OpenAndMapFile(RootDirectory, PathName, FileName,
566 &FileHandle, &SectionHandle, &ViewBase, NULL);
567 if (!NT_SUCCESS(Status))
568 {
569 DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", FileName, Status);
570 return FALSE; // Status;
571 }
572
573 /* Make sure it's a valid PE file */
574 if (!RtlImageNtHeader(ViewBase))
575 {
576 DPRINT1("File '%S' does not seem to be a valid PE, bail out\n", FileName);
577 Status = STATUS_INVALID_IMAGE_FORMAT;
578 goto UnmapFile;
579 }
580
581 /*
582 * Search for a valid executable version and vendor.
583 * NOTE: The module is loaded as a data file, it should be marked as such.
584 */
585 Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL);
586 if (!NT_SUCCESS(Status))
587 {
588 DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", FileName, Status);
589 goto UnmapFile;
590 }
591
592 Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen);
593 if (NT_SUCCESS(Status))
594 {
595 USHORT wCodePage = 0, wLangID = 0;
596 WCHAR FileInfo[MAX_PATH];
597
598 wCodePage = LOWORD(*(ULONG*)pvData);
599 wLangID = HIWORD(*(ULONG*)pvData);
600
601 StringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo),
602 L"StringFileInfo\\%04X%04X\\CompanyName",
603 wCodePage, wLangID);
604
605 Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen);
606
607 /* Fixup the Status in case pvData is NULL */
608 if (NT_SUCCESS(Status) && !pvData)
609 Status = STATUS_NOT_FOUND;
610
611 if (NT_SUCCESS(Status) /*&& pvData*/)
612 {
613 /* BufLen includes the NULL terminator count */
614 DPRINT1("Found version vendor: \"%S\" for file '%S'\n", pvData, FileName);
615
616 StringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength,
617 pvData, BufLen * sizeof(WCHAR));
618 VendorName->Length = wcslen(VendorName->Buffer) * sizeof(WCHAR);
619
620 Success = TRUE;
621 }
622 }
623
624 if (!NT_SUCCESS(Status))
625 DPRINT1("No version vendor found for file '%S'\n", FileName);
626
627 UnmapFile:
628 /* Finally, unmap and close the file */
629 UnMapFile(SectionHandle, ViewBase);
630 NtClose(FileHandle);
631
632 return Success;
633 }
634
635 //
636 // TODO: Instead of returning TRUE/FALSE, it would be nice to return
637 // a flag indicating:
638 // - whether the installation is actually valid;
639 // - if it's broken or not (aka. needs for repair, or just upgrading).
640 //
641 static BOOLEAN
642 IsValidNTOSInstallation(
643 IN HANDLE SystemRootDirectory OPTIONAL,
644 IN PCWSTR SystemRoot OPTIONAL)
645 {
646 BOOLEAN Success = FALSE;
647 USHORT i;
648 UNICODE_STRING VendorName;
649 WCHAR PathBuffer[MAX_PATH];
650
651 /*
652 * Use either the 'SystemRootDirectory' handle or the 'SystemRoot' string,
653 * depending on what the user gave to us in entry.
654 */
655 if (SystemRootDirectory)
656 SystemRoot = NULL;
657 // else SystemRootDirectory == NULL and SystemRoot is what it is.
658
659 /* If both the parameters are NULL we cannot do anything else more */
660 if (!SystemRootDirectory && !SystemRoot)
661 return FALSE;
662
663 // DoesPathExist(SystemRootDirectory, SystemRoot, L"System32\\"); etc...
664
665 /* Check for the existence of \SystemRoot\System32 */
666 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\");
667 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
668 {
669 // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status);
670 return FALSE;
671 }
672
673 /* Check for the existence of \SystemRoot\System32\drivers */
674 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\drivers\\");
675 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
676 {
677 // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status);
678 return FALSE;
679 }
680
681 /* Check for the existence of \SystemRoot\System32\config */
682 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"%s%s", SystemRoot ? SystemRoot : L"", L"System32\\config\\");
683 if (!DoesPathExist(SystemRootDirectory, PathBuffer))
684 {
685 // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status);
686 return FALSE;
687 }
688
689 #if 0
690 /*
691 * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config
692 * (but we don't check here whether they are actually valid).
693 */
694 if (!DoesFileExist(SystemRootDirectory, SystemRoot, L"System32\\config\\SYSTEM"))
695 {
696 // DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName, Status);
697 return FALSE;
698 }
699 if (!DoesFileExist(SystemRootDirectory, SystemRoot, L"System32\\config\\SOFTWARE"))
700 {
701 // DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName, Status);
702 return FALSE;
703 }
704 #endif
705
706 RtlInitEmptyUnicodeString(&VendorName, PathBuffer, sizeof(PathBuffer));
707
708 /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */
709 Success = CheckForValidPEAndVendor(SystemRootDirectory, SystemRoot, L"System32\\ntoskrnl.exe", &VendorName);
710 if (!Success)
711 DPRINT1("Kernel file ntoskrnl.exe is either not a PE file, or does not have any vendor?\n");
712
713 /* The kernel gives the OS its flavour */
714 if (Success)
715 {
716 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
717 {
718 Success = !!FindSubStrI(VendorName.Buffer, KnownVendors[i]);
719 if (Success)
720 {
721 /* We have found a correct vendor combination */
722 DPRINT1("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]);
723 break;
724 }
725 }
726 }
727
728 /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */
729
730 /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */
731 Success = CheckForValidPEAndVendor(SystemRootDirectory, SystemRoot, L"System32\\ntdll.dll", &VendorName);
732 if (!Success)
733 DPRINT1("User-mode file ntdll.dll is either not a PE file, or does not have any vendor?\n");
734 if (Success)
735 {
736 for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
737 {
738 if (!!FindSubStrI(VendorName.Buffer, KnownVendors[i]))
739 {
740 /* We have found a correct vendor combination */
741 DPRINT1("IsValidNTOSInstallation: The user-mode file ntdll.dll is from %S\n", KnownVendors[i]);
742 break;
743 }
744 }
745 }
746
747 return Success;
748 }
749
750 static VOID
751 DumpNTOSInstalls(
752 IN PGENERIC_LIST List)
753 {
754 PGENERIC_LIST_ENTRY Entry;
755 PNTOS_INSTALLATION NtOsInstall;
756 ULONG NtOsInstallsCount = GetNumberOfListEntries(List);
757
758 DPRINT1("There %s %d installation%s detected:\n",
759 NtOsInstallsCount >= 2 ? "are" : "is",
760 NtOsInstallsCount,
761 NtOsInstallsCount >= 2 ? "s" : "");
762
763 Entry = GetFirstListEntry(List);
764 while (Entry)
765 {
766 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry);
767 Entry = GetNextListEntry(Entry);
768
769 DPRINT1(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n",
770 NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber,
771 NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath);
772 }
773
774 DPRINT1("Done.\n");
775 }
776
777 static PNTOS_INSTALLATION
778 FindExistingNTOSInstall(
779 IN PGENERIC_LIST List,
780 IN PCWSTR SystemRootArcPath OPTIONAL,
781 IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
782 )
783 {
784 PGENERIC_LIST_ENTRY Entry;
785 PNTOS_INSTALLATION NtOsInstall;
786 UNICODE_STRING SystemArcPath;
787
788 /*
789 * We search either via ARC path or NT path.
790 * If both pointers are NULL then we fail straight away.
791 */
792 if (!SystemRootArcPath && !SystemRootNtPath)
793 return NULL;
794
795 RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath);
796
797 Entry = GetFirstListEntry(List);
798 while (Entry)
799 {
800 NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry);
801 Entry = GetNextListEntry(Entry);
802
803 /*
804 * Note that if both ARC paths are equal, then the corresponding
805 * NT paths must be the same. However, two ARC paths may be different
806 * but resolve into the same NT path.
807 */
808 if ( (SystemRootArcPath &&
809 RtlEqualUnicodeString(&NtOsInstall->SystemArcPath,
810 &SystemArcPath, TRUE)) ||
811 (SystemRootNtPath &&
812 RtlEqualUnicodeString(&NtOsInstall->SystemNtPath,
813 SystemRootNtPath, TRUE)) )
814 {
815 /* Found it! */
816 return NtOsInstall;
817 }
818 }
819
820 return NULL;
821 }
822
823 static PNTOS_INSTALLATION
824 AddNTOSInstallation(
825 IN PGENERIC_LIST List,
826 IN PCWSTR SystemRootArcPath,
827 IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
828 IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer
829 IN ULONG DiskNumber,
830 IN ULONG PartitionNumber,
831 IN PPARTENTRY PartEntry OPTIONAL,
832 IN PCWSTR InstallationName)
833 {
834 PNTOS_INSTALLATION NtOsInstall;
835 SIZE_T ArcPathLength, NtPathLength;
836 CHAR InstallNameA[MAX_PATH];
837
838 /* Is there already any installation with these settings? */
839 NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath);
840 if (NtOsInstall)
841 {
842 DPRINT1("An NTOS installation with name \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n",
843 NtOsInstall->InstallationName, NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath);
844 //
845 // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
846 // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
847 //
848 return NtOsInstall;
849 }
850
851 ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR);
852 // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR));
853 NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL);
854
855 /* None was found, so add a new one */
856 NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY,
857 sizeof(*NtOsInstall) +
858 ArcPathLength + NtPathLength);
859 if (!NtOsInstall)
860 return NULL;
861
862 NtOsInstall->DiskNumber = DiskNumber;
863 NtOsInstall->PartitionNumber = PartitionNumber;
864 NtOsInstall->PartEntry = PartEntry;
865
866 RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath,
867 (PWCHAR)(NtOsInstall + 1),
868 ArcPathLength);
869 RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength);
870 NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL);
871
872 RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath,
873 (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength),
874 NtPathLength);
875 RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath);
876 NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer +
877 (PathComponent - SystemRootNtPath->Buffer);
878
879 StringCchCopyW(NtOsInstall->InstallationName, ARRAYSIZE(NtOsInstall->InstallationName), InstallationName);
880
881 // Having the GENERIC_LIST storing the display item string plainly sucks...
882 StringCchPrintfA(InstallNameA, ARRAYSIZE(InstallNameA), "%S", InstallationName);
883 AppendGenericListEntry(List, InstallNameA, NtOsInstall, FALSE);
884
885 return NtOsInstall;
886 }
887
888 static VOID
889 FindNTOSInstallations(
890 IN OUT PGENERIC_LIST List,
891 IN PPARTLIST PartList,
892 IN PPARTENTRY PartEntry)
893 {
894 NTSTATUS Status;
895 ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber;
896 ULONG PartitionNumber = PartEntry->PartitionNumber;
897 HANDLE PartitionHandle, FileHandle;
898 OBJECT_ATTRIBUTES ObjectAttributes;
899 IO_STATUS_BLOCK IoStatusBlock;
900 UNICODE_STRING PartitionRootPath;
901 UINT i;
902 HANDLE SectionHandle;
903 // SIZE_T ViewSize;
904 ULONG FileSize;
905 PVOID ViewBase;
906 WCHAR PathBuffer[MAX_PATH];
907
908 /* Set PartitionRootPath */
909 StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer),
910 L"\\Device\\Harddisk%lu\\Partition%lu\\",
911 DiskNumber, PartitionNumber);
912 RtlInitUnicodeString(&PartitionRootPath, PathBuffer);
913 DPRINT1("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath);
914
915 /* Open the partition */
916 InitializeObjectAttributes(&ObjectAttributes,
917 &PartitionRootPath,
918 OBJ_CASE_INSENSITIVE,
919 NULL,
920 NULL);
921 Status = NtOpenFile(&PartitionHandle,
922 FILE_LIST_DIRECTORY | SYNCHRONIZE,
923 &ObjectAttributes,
924 &IoStatusBlock,
925 FILE_SHARE_READ | FILE_SHARE_WRITE,
926 FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
927 if (!NT_SUCCESS(Status))
928 {
929 DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status);
930 return;
931 }
932
933 /* Try to see whether we recognize some NT boot loaders */
934 for (i = 0; i < ARRAYSIZE(NtosBootLoaders); ++i)
935 {
936 /* Check whether the loader executable exists */
937 if (!DoesFileExist(PartitionHandle, NULL, NtosBootLoaders[i].LoaderExecutable))
938 {
939 /* The loader does not exist, continue with another one */
940 DPRINT1("Loader executable '%S' does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderExecutable);
941 continue;
942 }
943
944 /* Check whether the loader configuration file exists */
945 Status = OpenAndMapFile(PartitionHandle, NULL, NtosBootLoaders[i].LoaderConfigurationFile,
946 &FileHandle, &SectionHandle, &ViewBase, &FileSize);
947 if (!NT_SUCCESS(Status))
948 {
949 /* The loader does not exist, continue with another one */
950 // FIXME: Consider it might be optional??
951 DPRINT1("Loader configuration file '%S' does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderConfigurationFile);
952 continue;
953 }
954
955 /* The loader configuration file exists, interpret it to find valid installations */
956 DPRINT1("Analyse the OS installations inside '%S' in disk #%d, partition #%d\n",
957 NtosBootLoaders[i].LoaderConfigurationFile, DiskNumber, PartitionNumber);
958 switch (NtosBootLoaders[i].Type)
959 {
960 case FreeLdr:
961 Status = FreeLdrEnumerateInstallations(List, PartList, ViewBase, FileSize);
962 break;
963
964 case NtLdr:
965 Status = NtLdrEnumerateInstallations(List, PartList, ViewBase, FileSize);
966 break;
967
968 default:
969 DPRINT1("Loader type %d is currently unsupported!\n", NtosBootLoaders[i].Type);
970 Status = STATUS_SUCCESS;
971 }
972
973 /* Finally, unmap and close the file */
974 UnMapFile(SectionHandle, ViewBase);
975 NtClose(FileHandle);
976 }
977
978 /* Close the partition */
979 NtClose(PartitionHandle);
980 }
981
982 // static
983 FORCEINLINE BOOLEAN
984 ShouldICheckThisPartition(
985 IN PPARTENTRY PartEntry)
986 {
987 if (!PartEntry)
988 return FALSE;
989
990 return PartEntry->IsPartitioned &&
991 !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
992 !PartEntry->New &&
993 (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */);
994 }
995
996 // EnumerateNTOSInstallations
997 PGENERIC_LIST
998 CreateNTOSInstallationsList(
999 IN PPARTLIST PartList)
1000 {
1001 PGENERIC_LIST List;
1002 PLIST_ENTRY Entry, Entry2;
1003 PDISKENTRY DiskEntry;
1004 PPARTENTRY PartEntry;
1005
1006 List = CreateGenericList();
1007 if (List == NULL)
1008 return NULL;
1009
1010 /* Loop each available disk ... */
1011 Entry = PartList->DiskListHead.Flink;
1012 while (Entry != &PartList->DiskListHead)
1013 {
1014 DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
1015 Entry = Entry->Flink;
1016
1017 DPRINT1("Disk #%d\n", DiskEntry->DiskNumber);
1018
1019 /* ... and for each disk, loop each available partition */
1020
1021 /* First, the primary partitions */
1022 Entry2 = DiskEntry->PrimaryPartListHead.Flink;
1023 while (Entry2 != &DiskEntry->PrimaryPartListHead)
1024 {
1025 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
1026 Entry2 = Entry2->Flink;
1027
1028 ASSERT(PartEntry->DiskEntry == DiskEntry);
1029
1030 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",
1031 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
1032 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
1033 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
1034 PartEntry->New ? "Yes" : "No",
1035 PartEntry->AutoCreate ? "Yes" : "No",
1036 PartEntry->FormatState,
1037 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
1038
1039 if (ShouldICheckThisPartition(PartEntry))
1040 FindNTOSInstallations(List, PartList, PartEntry);
1041 }
1042
1043 /* Then, the logical partitions (present in the extended partition) */
1044 Entry2 = DiskEntry->LogicalPartListHead.Flink;
1045 while (Entry2 != &DiskEntry->LogicalPartListHead)
1046 {
1047 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
1048 Entry2 = Entry2->Flink;
1049
1050 ASSERT(PartEntry->DiskEntry == DiskEntry);
1051
1052 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",
1053 PartEntry->PartitionNumber, PartEntry->PartitionIndex,
1054 PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
1055 PartEntry->IsPartitioned ? "TRUE" : "FALSE",
1056 PartEntry->New ? "Yes" : "No",
1057 PartEntry->AutoCreate ? "Yes" : "No",
1058 PartEntry->FormatState,
1059 ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
1060
1061 if (ShouldICheckThisPartition(PartEntry))
1062 FindNTOSInstallations(List, PartList, PartEntry);
1063 }
1064 }
1065
1066 /**** Debugging: List all the collected installations ****/
1067 DumpNTOSInstalls(List);
1068
1069 return List;
1070 }
1071
1072 /* EOF */