/* * PROJECT: ReactOS Setup Library * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: NT 5.x family (MS Windows <= 2003, and ReactOS) * operating systems detection code. * COPYRIGHT: Copyright 2017-2018 Hermes Belusca-Maito */ /* INCLUDES *****************************************************************/ #include "precomp.h" #include "ntverrsrc.h" // #include "arcname.h" #include "filesup.h" #include "genlist.h" #include "inicache.h" #include "partlist.h" #include "arcname.h" #include "osdetect.h" // HACK! #include #define NDEBUG #include /* GLOBALS ******************************************************************/ /* Language-independent Vendor strings */ static const PCWSTR KnownVendors[] = { L"ReactOS", L"Microsoft" }; /* FUNCTIONS ****************************************************************/ #if 0 BOOL IsWindowsOS(VOID) { // TODO? : // Load the "SystemRoot\System32\Config\SOFTWARE" hive and mount it, // then go to (SOFTWARE\\)Microsoft\\Windows NT\\CurrentVersion, // check the REG_SZ value "ProductName" and see whether it's "Windows" // or "ReactOS". One may also check the REG_SZ "CurrentVersion" value, // the REG_SZ "SystemRoot" and "PathName" values (what are the differences??). // // Optionally, looking at the SYSTEM hive, CurrentControlSet\\Control, // REG_SZ values "SystemBootDevice" (and "FirmwareBootDevice" ??)... // /* ReactOS reports as Windows NT 5.2 */ HKEY hKey = NULL; if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { LONG ret; DWORD dwType = 0, dwBufSize = 0; ret = RegQueryValueExW(hKey, L"ProductName", NULL, &dwType, NULL, &dwBufSize); if (ret == ERROR_SUCCESS && dwType == REG_SZ) { LPTSTR lpszProductName = (LPTSTR)MemAlloc(0, dwBufSize); RegQueryValueExW(hKey, L"ProductName", NULL, &dwType, (LPBYTE)lpszProductName, &dwBufSize); bIsWindowsOS = (FindSubStrI(lpszProductName, _T("Windows")) != NULL); MemFree(lpszProductName); } RegCloseKey(hKey); } return bIsWindowsOS; } #endif typedef enum _NTOS_BOOT_LOADER_TYPE { FreeLdr, // ReactOS' FreeLDR NtLdr, // Windows <= 2k3 NTLDR // BootMgr, // Vista+ BCD-oriented BOOTMGR } NTOS_BOOT_LOADER_TYPE; typedef struct _NTOS_BOOT_LOADER_FILES { NTOS_BOOT_LOADER_TYPE Type; PCWSTR LoaderExecutable; PCWSTR LoaderConfigurationFile; // EnumerateInstallations; } NTOS_BOOT_LOADER_FILES, *PNTOS_BOOT_LOADER_FILES; // Question 1: What if config file is optional? // Question 2: What if many config files are possible? NTOS_BOOT_LOADER_FILES NtosBootLoaders[] = { {FreeLdr, L"freeldr.sys", L"freeldr.ini"}, {NtLdr , L"ntldr" , L"boot.ini"}, {NtLdr , L"setupldr" , L"txtsetup.sif"}, // {BootMgr, L"bootmgr" , ???} }; static BOOLEAN IsValidNTOSInstallation_UStr( IN PUNICODE_STRING SystemRootPath); /*static*/ BOOLEAN IsValidNTOSInstallation( IN PCWSTR SystemRoot); static PNTOS_INSTALLATION FindExistingNTOSInstall( IN PGENERIC_LIST List, IN PCWSTR SystemRootArcPath OPTIONAL, IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ? ); static PNTOS_INSTALLATION AddNTOSInstallation( IN PGENERIC_LIST List, IN PCWSTR SystemRootArcPath, IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ? IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer IN ULONG DiskNumber, IN ULONG PartitionNumber, IN PPARTENTRY PartEntry OPTIONAL, IN PCWSTR InstallationName); static NTSTATUS FreeLdrEnumerateInstallations( IN OUT PGENERIC_LIST List, IN PPARTLIST PartList, // IN PPARTENTRY PartEntry, IN PCHAR FileBuffer, IN ULONG FileLength) { NTSTATUS Status; PINICACHE IniCache; PINICACHEITERATOR Iterator; PINICACHESECTION IniSection, OsIniSection; PWCHAR SectionName, KeyData; UNICODE_STRING InstallName; PNTOS_INSTALLATION NtOsInstall; UNICODE_STRING SystemRootPath; WCHAR SystemRoot[MAX_PATH]; WCHAR InstallNameW[MAX_PATH]; /* Open an *existing* FreeLdr.ini configuration file */ Status = IniCacheLoadFromMemory(&IniCache, FileBuffer, FileLength, FALSE); if (!NT_SUCCESS(Status)) return Status; /* Get the "Operating Systems" section */ IniSection = IniCacheGetSection(IniCache, L"Operating Systems"); if (IniSection == NULL) { IniCacheDestroy(IniCache); return STATUS_UNSUCCESSFUL; } /* Enumerate all the valid installations */ Iterator = IniCacheFindFirstValue(IniSection, &SectionName, &KeyData); if (!Iterator) goto Quit; do { // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni). if (KeyData[0] == L'"') { /* Quoted name, copy up to the closing quote */ PWCHAR Begin = &KeyData[1]; PWCHAR End = wcschr(Begin, L'"'); if (!End) End = Begin + wcslen(Begin); RtlInitEmptyUnicodeString(&InstallName, Begin, (ULONG_PTR)End - (ULONG_PTR)Begin); InstallName.Length = InstallName.MaximumLength; } else { /* Non-quoted name, copy everything */ RtlInitUnicodeString(&InstallName, KeyData); } DPRINT1("Possible installation '%wZ' in OS section '%S'\n", &InstallName, SectionName); /* Search for an existing ReactOS entry */ OsIniSection = IniCacheGetSection(IniCache, SectionName); if (!OsIniSection) continue; /* Check for supported boot type "Windows2003" */ Status = IniCacheGetKey(OsIniSection, L"BootType", &KeyData); if (NT_SUCCESS(Status)) { // TODO: What to do with "Windows" ; "WindowsNT40" ; "ReactOSSetup" ? if ((KeyData == NULL) || ( (_wcsicmp(KeyData, L"Windows2003") != 0) && (_wcsicmp(KeyData, L"\"Windows2003\"") != 0) )) { /* This is not a ReactOS entry */ continue; } } else { /* Certainly not a ReactOS installation */ continue; } /* BootType is Windows2003. Now check SystemPath. */ Status = IniCacheGetKey(OsIniSection, L"SystemPath", &KeyData); if (!NT_SUCCESS(Status)) { DPRINT1(" A Win2k3 install '%wZ' without an ARC path?!\n", &InstallName); continue; } DPRINT1(" Found a candidate Win2k3 install '%wZ' with ARC path '%S'\n", &InstallName, KeyData); // TODO: Normalize the ARC path. /* * Check whether we already have an installation with this ARC path. * If this is the case, stop there. */ NtOsInstall = FindExistingNTOSInstall(List, KeyData, NULL); if (NtOsInstall) { DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n", NtOsInstall->InstallationName, &NtOsInstall->SystemArcPath); continue; } /* * Convert the ARC path into an NT path, from which we will deduce * the real disk drive & partition on which the candidate installation * resides, as well verifying whether it is indeed an NTOS installation. */ RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot)); if (!ArcPathToNtPath(&SystemRootPath, KeyData, PartList)) { DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", KeyData); continue; } DPRINT1("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n", KeyData, &SystemRootPath); /* * Check whether we already have an installation with this NT path. * If this is the case, stop there. */ NtOsInstall = FindExistingNTOSInstall(List, NULL /*KeyData*/, &SystemRootPath); if (NtOsInstall) { DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n", NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath); continue; } /* Set SystemRootPath */ DPRINT1("FreeLdrEnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath); if (IsValidNTOSInstallation_UStr(&SystemRootPath)) { ULONG DiskNumber = 0, PartitionNumber = 0; PCWSTR PathComponent = NULL; PDISKENTRY DiskEntry = NULL; PPARTENTRY PartEntry = NULL; DPRINT1("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", KeyData, &SystemRootPath); /* From the NT path, compute the disk, partition and path components */ if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent)) { DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n", &SystemRootPath, DiskNumber, PartitionNumber, PathComponent); /* Retrieve the corresponding disk and partition */ if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry)) DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber); } else { DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath); } if (PartEntry && PartEntry->DriveLetter) { /* We have retrieved a partition that is mounted */ StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%wZ\"", PartEntry->DriveLetter, PathComponent, &InstallName); } else { /* We failed somewhere, just show the NT path */ StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%wZ\"", &SystemRootPath, &InstallName); } AddNTOSInstallation(List, KeyData, &SystemRootPath, PathComponent, DiskNumber, PartitionNumber, PartEntry, InstallNameW); } } while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData)); IniCacheFindClose(Iterator); Quit: IniCacheDestroy(IniCache); return STATUS_SUCCESS; } static NTSTATUS NtLdrEnumerateInstallations( IN OUT PGENERIC_LIST List, IN PPARTLIST PartList, // IN PPARTENTRY PartEntry, IN PCHAR FileBuffer, IN ULONG FileLength) { NTSTATUS Status; PINICACHE IniCache; PINICACHEITERATOR Iterator; PINICACHESECTION IniSection; PWCHAR SectionName, KeyData; UNICODE_STRING InstallName; PNTOS_INSTALLATION NtOsInstall; UNICODE_STRING SystemRootPath; WCHAR SystemRoot[MAX_PATH]; WCHAR InstallNameW[MAX_PATH]; /* Open an *existing* FreeLdr.ini configuration file */ Status = IniCacheLoadFromMemory(&IniCache, FileBuffer, FileLength, FALSE); if (!NT_SUCCESS(Status)) return Status; /* Get the "Operating Systems" section */ IniSection = IniCacheGetSection(IniCache, L"operating systems"); if (IniSection == NULL) { IniCacheDestroy(IniCache); return STATUS_UNSUCCESSFUL; } /* Enumerate all the valid installations */ Iterator = IniCacheFindFirstValue(IniSection, &SectionName, &KeyData); if (!Iterator) goto Quit; do { // FIXME: Poor-man quotes removal (improvement over bootsup.c:UpdateFreeLoaderIni). if (KeyData[0] == L'"') { /* Quoted name, copy up to the closing quote */ PWCHAR Begin = &KeyData[1]; PWCHAR End = wcschr(Begin, L'"'); if (!End) End = Begin + wcslen(Begin); RtlInitEmptyUnicodeString(&InstallName, Begin, (ULONG_PTR)End - (ULONG_PTR)Begin); InstallName.Length = InstallName.MaximumLength; } else { /* Non-quoted name, copy everything */ RtlInitUnicodeString(&InstallName, KeyData); } DPRINT1("Possible installation '%wZ' with ARC path '%S'\n", &InstallName, SectionName); DPRINT1(" Found a Win2k3 install '%wZ' with ARC path '%S'\n", &InstallName, SectionName); // TODO: Normalize the ARC path. /* * Check whether we already have an installation with this ARC path. * If this is the case, stop there. */ NtOsInstall = FindExistingNTOSInstall(List, SectionName, NULL); if (NtOsInstall) { DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n", NtOsInstall->InstallationName, &NtOsInstall->SystemArcPath); continue; } /* * Convert the ARC path into an NT path, from which we will deduce * the real disk drive & partition on which the candidate installation * resides, as well verifying whether it is indeed an NTOS installation. */ RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot)); if (!ArcPathToNtPath(&SystemRootPath, SectionName, PartList)) { DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", SectionName); continue; } DPRINT1("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n", SectionName, &SystemRootPath); /* * Check whether we already have an installation with this NT path. * If this is the case, stop there. */ NtOsInstall = FindExistingNTOSInstall(List, NULL /*SectionName*/, &SystemRootPath); if (NtOsInstall) { DPRINT1(" An NTOS installation with name \"%S\" already exists in SystemRoot '%wZ'\n", NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath); continue; } /* Set SystemRootPath */ DPRINT1("NtLdrEnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath); if (IsValidNTOSInstallation_UStr(&SystemRootPath)) { ULONG DiskNumber = 0, PartitionNumber = 0; PCWSTR PathComponent = NULL; PDISKENTRY DiskEntry = NULL; PPARTENTRY PartEntry = NULL; DPRINT1("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n", SectionName, &SystemRootPath); /* From the NT path, compute the disk, partition and path components */ if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent)) { DPRINT1("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n", &SystemRootPath, DiskNumber, PartitionNumber, PathComponent); /* Retrieve the corresponding disk and partition */ if (!GetDiskOrPartition(PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry)) DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n", DiskNumber, PartitionNumber); } else { DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath); } if (PartEntry && PartEntry->DriveLetter) { /* We have retrieved a partition that is mounted */ StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%C:%s \"%wZ\"", PartEntry->DriveLetter, PathComponent, &InstallName); } else { /* We failed somewhere, just show the NT path */ StringCchPrintfW(InstallNameW, ARRAYSIZE(InstallNameW), L"%wZ \"%wZ\"", &SystemRootPath, &InstallName); } AddNTOSInstallation(List, SectionName, &SystemRootPath, PathComponent, DiskNumber, PartitionNumber, PartEntry, InstallNameW); } } while (IniCacheFindNextValue(Iterator, &SectionName, &KeyData)); IniCacheFindClose(Iterator); Quit: IniCacheDestroy(IniCache); return STATUS_SUCCESS; } /* * FindSubStrI(PCWSTR str, PCWSTR strSearch) : * Searches for a sub-string 'strSearch' inside 'str', similarly to what * wcsstr(str, strSearch) does, but ignores the case during the comparisons. */ PCWSTR FindSubStrI(PCWSTR str, PCWSTR strSearch) { PCWSTR cp = str; PCWSTR s1, s2; if (!*strSearch) return str; while (*cp) { s1 = cp; s2 = strSearch; while (*s1 && *s2 && (towupper(*s1) == towupper(*s2))) ++s1, ++s2; if (!*s2) return cp; ++cp; } return NULL; } static BOOLEAN CheckForValidPEAndVendor( IN HANDLE RootDirectory OPTIONAL, IN PCWSTR PathNameToFile, OUT PUNICODE_STRING VendorName ) { BOOLEAN Success = FALSE; NTSTATUS Status; HANDLE FileHandle, SectionHandle; // SIZE_T ViewSize; PVOID ViewBase; PVOID VersionBuffer = NULL; // Read-only PVOID pvData = NULL; UINT BufLen = 0; if (VendorName->MaximumLength < sizeof(UNICODE_NULL)) return FALSE; *VendorName->Buffer = UNICODE_NULL; VendorName->Length = 0; Status = OpenAndMapFile(RootDirectory, PathNameToFile, &FileHandle, &SectionHandle, &ViewBase, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status); return FALSE; // Status; } /* Make sure it's a valid PE file */ if (!RtlImageNtHeader(ViewBase)) { DPRINT1("File '%S' does not seem to be a valid PE, bail out\n", PathNameToFile); Status = STATUS_INVALID_IMAGE_FORMAT; goto UnmapFile; } /* * Search for a valid executable version and vendor. * NOTE: The module is loaded as a data file, it should be marked as such. */ Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", PathNameToFile, Status); goto UnmapFile; } Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen); if (NT_SUCCESS(Status)) { USHORT wCodePage = 0, wLangID = 0; WCHAR FileInfo[MAX_PATH]; wCodePage = LOWORD(*(ULONG*)pvData); wLangID = HIWORD(*(ULONG*)pvData); StringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo), L"StringFileInfo\\%04X%04X\\CompanyName", wCodePage, wLangID); Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen); /* Fixup the Status in case pvData is NULL */ if (NT_SUCCESS(Status) && !pvData) Status = STATUS_NOT_FOUND; if (NT_SUCCESS(Status) /*&& pvData*/) { /* BufLen includes the NULL terminator count */ DPRINT1("Found version vendor: \"%S\" for file '%S'\n", pvData, PathNameToFile); StringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength, pvData, BufLen * sizeof(WCHAR)); VendorName->Length = wcslen(VendorName->Buffer) * sizeof(WCHAR); Success = TRUE; } } if (!NT_SUCCESS(Status)) DPRINT1("No version vendor found for file '%S'\n", PathNameToFile); UnmapFile: /* Finally, unmap and close the file */ UnMapFile(SectionHandle, ViewBase); NtClose(FileHandle); return Success; } // // TODO: Instead of returning TRUE/FALSE, it would be nice to return // a flag indicating: // - whether the installation is actually valid; // - if it's broken or not (aka. needs for repair, or just upgrading). // static BOOLEAN IsValidNTOSInstallationByHandle( IN HANDLE SystemRootDirectory) { BOOLEAN Success = FALSE; USHORT i; UNICODE_STRING VendorName; WCHAR VendorNameBuffer[MAX_PATH]; /* Check for the existence of \SystemRoot\System32 */ if (!DoesPathExist(SystemRootDirectory, L"System32\\")) { // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status); return FALSE; } /* Check for the existence of \SystemRoot\System32\drivers */ if (!DoesPathExist(SystemRootDirectory, L"System32\\drivers\\")) { // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status); return FALSE; } /* Check for the existence of \SystemRoot\System32\config */ if (!DoesPathExist(SystemRootDirectory, L"System32\\config\\")) { // DPRINT1("Failed to open directory '%wZ', Status 0x%08lx\n", &FileName, Status); return FALSE; } #if 0 /* * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config * (but we don't check here whether they are actually valid). */ if (!DoesFileExist(SystemRootDirectory, L"System32\\config\\SYSTEM")) { // DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName, Status); return FALSE; } if (!DoesFileExist(SystemRootDirectory, L"System32\\config\\SOFTWARE")) { // DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName, Status); return FALSE; } #endif RtlInitEmptyUnicodeString(&VendorName, VendorNameBuffer, sizeof(VendorNameBuffer)); /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */ Success = CheckForValidPEAndVendor(SystemRootDirectory, L"System32\\ntoskrnl.exe", &VendorName); if (!Success) DPRINT1("Kernel file ntoskrnl.exe is either not a PE file, or does not have any vendor?\n"); /* The kernel gives the OS its flavour */ if (Success) { for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) { Success = !!FindSubStrI(VendorName.Buffer, KnownVendors[i]); if (Success) { /* We have found a correct vendor combination */ DPRINT1("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]); break; } } } /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */ /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */ Success = CheckForValidPEAndVendor(SystemRootDirectory, L"System32\\ntdll.dll", &VendorName); if (!Success) DPRINT1("User-mode file ntdll.dll is either not a PE file, or does not have any vendor?\n"); if (Success) { for (i = 0; i < ARRAYSIZE(KnownVendors); ++i) { if (!!FindSubStrI(VendorName.Buffer, KnownVendors[i])) { /* We have found a correct vendor combination */ DPRINT1("IsValidNTOSInstallation: The user-mode file ntdll.dll is from %S\n", KnownVendors[i]); break; } } } return Success; } static BOOLEAN IsValidNTOSInstallation_UStr( IN PUNICODE_STRING SystemRootPath) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; HANDLE SystemRootDirectory; BOOLEAN Success; /* Open SystemRootPath */ InitializeObjectAttributes(&ObjectAttributes, SystemRootPath, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenFile(&SystemRootDirectory, FILE_LIST_DIRECTORY | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", SystemRootPath, Status); return FALSE; } Success = IsValidNTOSInstallationByHandle(SystemRootDirectory); /* Done! */ NtClose(SystemRootDirectory); return Success; } /*static*/ BOOLEAN IsValidNTOSInstallation( IN PCWSTR SystemRoot) { UNICODE_STRING SystemRootPath; RtlInitUnicodeString(&SystemRootPath, SystemRoot); return IsValidNTOSInstallationByHandle(&SystemRootPath); } static VOID DumpNTOSInstalls( IN PGENERIC_LIST List) { PGENERIC_LIST_ENTRY Entry; PNTOS_INSTALLATION NtOsInstall; ULONG NtOsInstallsCount = GetNumberOfListEntries(List); DPRINT1("There %s %d installation%s detected:\n", NtOsInstallsCount >= 2 ? "are" : "is", NtOsInstallsCount, NtOsInstallsCount >= 2 ? "s" : ""); Entry = GetFirstListEntry(List); while (Entry) { NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry); Entry = GetNextListEntry(Entry); DPRINT1(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n", NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath); } DPRINT1("Done.\n"); } static PNTOS_INSTALLATION FindExistingNTOSInstall( IN PGENERIC_LIST List, IN PCWSTR SystemRootArcPath OPTIONAL, IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ? ) { PGENERIC_LIST_ENTRY Entry; PNTOS_INSTALLATION NtOsInstall; UNICODE_STRING SystemArcPath; /* * We search either via ARC path or NT path. * If both pointers are NULL then we fail straight away. */ if (!SystemRootArcPath && !SystemRootNtPath) return NULL; RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath); Entry = GetFirstListEntry(List); while (Entry) { NtOsInstall = (PNTOS_INSTALLATION)GetListEntryUserData(Entry); Entry = GetNextListEntry(Entry); /* * Note that if both ARC paths are equal, then the corresponding * NT paths must be the same. However, two ARC paths may be different * but resolve into the same NT path. */ if ( (SystemRootArcPath && RtlEqualUnicodeString(&NtOsInstall->SystemArcPath, &SystemArcPath, TRUE)) || (SystemRootNtPath && RtlEqualUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath, TRUE)) ) { /* Found it! */ return NtOsInstall; } } return NULL; } static PNTOS_INSTALLATION AddNTOSInstallation( IN PGENERIC_LIST List, IN PCWSTR SystemRootArcPath, IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ? IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer IN ULONG DiskNumber, IN ULONG PartitionNumber, IN PPARTENTRY PartEntry OPTIONAL, IN PCWSTR InstallationName) { PNTOS_INSTALLATION NtOsInstall; SIZE_T ArcPathLength, NtPathLength; CHAR InstallNameA[MAX_PATH]; /* Is there already any installation with these settings? */ NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath); if (NtOsInstall) { DPRINT1("An NTOS installation with name \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n", NtOsInstall->InstallationName, NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath); // // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE... // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file. // return NtOsInstall; } ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR); // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR)); NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL); /* None was found, so add a new one */ NtOsInstall = RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, sizeof(*NtOsInstall) + ArcPathLength + NtPathLength); if (!NtOsInstall) return NULL; NtOsInstall->DiskNumber = DiskNumber; NtOsInstall->PartitionNumber = PartitionNumber; NtOsInstall->PartEntry = PartEntry; RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath, (PWCHAR)(NtOsInstall + 1), ArcPathLength); RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength); NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL); RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath, (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength), NtPathLength); RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath); NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer + (PathComponent - SystemRootNtPath->Buffer); StringCchCopyW(NtOsInstall->InstallationName, ARRAYSIZE(NtOsInstall->InstallationName), InstallationName); // Having the GENERIC_LIST storing the display item string plainly sucks... StringCchPrintfA(InstallNameA, ARRAYSIZE(InstallNameA), "%S", InstallationName); AppendGenericListEntry(List, InstallNameA, NtOsInstall, FALSE); return NtOsInstall; } static VOID FindNTOSInstallations( IN OUT PGENERIC_LIST List, IN PPARTLIST PartList, IN PPARTENTRY PartEntry) { NTSTATUS Status; ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber; ULONG PartitionNumber = PartEntry->PartitionNumber; HANDLE PartitionHandle, FileHandle; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING PartitionRootPath; UINT i; HANDLE SectionHandle; // SIZE_T ViewSize; ULONG FileSize; PVOID ViewBase; WCHAR PathBuffer[MAX_PATH]; /* Set PartitionRootPath */ StringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer), L"\\Device\\Harddisk%lu\\Partition%lu\\", DiskNumber, PartitionNumber); RtlInitUnicodeString(&PartitionRootPath, PathBuffer); DPRINT1("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath); /* Open the partition */ InitializeObjectAttributes(&ObjectAttributes, &PartitionRootPath, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenFile(&PartitionHandle, FILE_LIST_DIRECTORY | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status); return; } /* Try to see whether we recognize some NT boot loaders */ for (i = 0; i < ARRAYSIZE(NtosBootLoaders); ++i) { /* Check whether the loader executable exists */ if (!DoesFileExist(PartitionHandle, NtosBootLoaders[i].LoaderExecutable)) { /* The loader does not exist, continue with another one */ DPRINT1("Loader executable '%S' does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderExecutable); continue; } /* Check whether the loader configuration file exists */ Status = OpenAndMapFile(PartitionHandle, NtosBootLoaders[i].LoaderConfigurationFile, &FileHandle, &SectionHandle, &ViewBase, &FileSize); if (!NT_SUCCESS(Status)) { /* The loader does not exist, continue with another one */ // FIXME: Consider it might be optional?? DPRINT1("Loader configuration file '%S' does not exist, continue with another one...\n", NtosBootLoaders[i].LoaderConfigurationFile); continue; } /* The loader configuration file exists, interpret it to find valid installations */ DPRINT1("Analyse the OS installations inside '%S' in disk #%d, partition #%d\n", NtosBootLoaders[i].LoaderConfigurationFile, DiskNumber, PartitionNumber); switch (NtosBootLoaders[i].Type) { case FreeLdr: Status = FreeLdrEnumerateInstallations(List, PartList, ViewBase, FileSize); break; case NtLdr: Status = NtLdrEnumerateInstallations(List, PartList, ViewBase, FileSize); break; default: DPRINT1("Loader type %d is currently unsupported!\n", NtosBootLoaders[i].Type); Status = STATUS_SUCCESS; } /* Finally, unmap and close the file */ UnMapFile(SectionHandle, ViewBase); NtClose(FileHandle); } /* Close the partition */ NtClose(PartitionHandle); } // static FORCEINLINE BOOLEAN ShouldICheckThisPartition( IN PPARTENTRY PartEntry) { if (!PartEntry) return FALSE; return PartEntry->IsPartitioned && !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ && !PartEntry->New && (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */); } // EnumerateNTOSInstallations PGENERIC_LIST CreateNTOSInstallationsList( IN PPARTLIST PartList) { PGENERIC_LIST List; PLIST_ENTRY Entry, Entry2; PDISKENTRY DiskEntry; PPARTENTRY PartEntry; List = CreateGenericList(); if (List == NULL) return NULL; /* Loop each available disk ... */ Entry = PartList->DiskListHead.Flink; while (Entry != &PartList->DiskListHead) { DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry); Entry = Entry->Flink; DPRINT1("Disk #%d\n", DiskEntry->DiskNumber); /* ... and for each disk, loop each available partition */ /* First, the primary partitions */ Entry2 = DiskEntry->PrimaryPartListHead.Flink; while (Entry2 != &DiskEntry->PrimaryPartListHead) { PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry); Entry2 = Entry2->Flink; ASSERT(PartEntry->DiskEntry == DiskEntry); 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", PartEntry->PartitionNumber, PartEntry->PartitionIndex, PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE", PartEntry->IsPartitioned ? "TRUE" : "FALSE", PartEntry->New ? "Yes" : "No", PartEntry->AutoCreate ? "Yes" : "No", PartEntry->FormatState, ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!"); if (ShouldICheckThisPartition(PartEntry)) FindNTOSInstallations(List, PartList, PartEntry); } /* Then, the logical partitions (present in the extended partition) */ Entry2 = DiskEntry->LogicalPartListHead.Flink; while (Entry2 != &DiskEntry->LogicalPartListHead) { PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry); Entry2 = Entry2->Flink; ASSERT(PartEntry->DiskEntry == DiskEntry); 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", PartEntry->PartitionNumber, PartEntry->PartitionIndex, PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE", PartEntry->IsPartitioned ? "TRUE" : "FALSE", PartEntry->New ? "Yes" : "No", PartEntry->AutoCreate ? "Yes" : "No", PartEntry->FormatState, ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!"); if (ShouldICheckThisPartition(PartEntry)) FindNTOSInstallations(List, PartList, PartEntry); } } /**** Debugging: List all the collected installations ****/ DumpNTOSInstalls(List); return List; } /* EOF */