ReactOS  0.4.13-dev-544-gede3fdd
osdetect.c
Go to the documentation of this file.
1 /*
2  * PROJECT: ReactOS Setup Library
3  * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE: NT 5.x family (MS Windows <= 2003, and ReactOS)
5  * operating systems detection code.
6  * COPYRIGHT: Copyright 2017-2018 Hermes Belusca-Maito
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include "precomp.h"
12 
13 #include "ntverrsrc.h"
14 // #include "arcname.h"
15 #include "bldrsup.h"
16 #include "filesup.h"
17 #include "genlist.h"
18 #include "partlist.h"
19 #include "arcname.h"
20 #include "osdetect.h"
21 
22 #define NDEBUG
23 #include <debug.h>
24 
25 
26 /* GLOBALS ******************************************************************/
27 
28 /* Language-independent Vendor strings */
30 
31 
32 /* FUNCTIONS ****************************************************************/
33 
34 static BOOLEAN
36  IN PUNICODE_STRING SystemRootPath,
37  OUT PUSHORT Machine OPTIONAL,
38  OUT PUNICODE_STRING VendorName OPTIONAL);
39 
40 static PNTOS_INSTALLATION
43  IN PCWSTR SystemRootArcPath OPTIONAL,
44  IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
45  );
46 
47 static PNTOS_INSTALLATION
50  IN PCWSTR InstallationName,
51  IN USHORT Machine,
52  IN PCWSTR VendorName,
53  IN PCWSTR SystemRootArcPath,
54  IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
55  IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer
56  IN ULONG DiskNumber,
58  IN PPARTENTRY PartEntry OPTIONAL);
59 
60 typedef struct _ENUM_INSTALLS_DATA
61 {
64  // IN PPARTENTRY PartEntry;
66 
67 // PENUM_BOOT_ENTRIES_ROUTINE
68 static NTSTATUS
69 NTAPI
72  IN PBOOT_STORE_ENTRY BootEntry,
74 {
76  PNTOS_OPTIONS Options = (PNTOS_OPTIONS)&BootEntry->OsOptions;
77  PNTOS_INSTALLATION NtOsInstall;
78 
79  ULONG DiskNumber = 0, PartitionNumber = 0;
81  PDISKENTRY DiskEntry = NULL;
82  PPARTENTRY PartEntry = NULL;
83 
84  UNICODE_STRING SystemRootPath;
86 
87  USHORT Machine;
88  UNICODE_STRING VendorName;
89  WCHAR VendorNameBuffer[MAX_PATH];
90 
91 
92  /* We have a boot entry */
93 
94  /* Check for supported boot type "Windows2003" */
95  if (BootEntry->OsOptionsLength < sizeof(NTOS_OPTIONS) ||
96  RtlCompareMemory(&BootEntry->OsOptions /* Signature */,
100  {
101  /* This is not a ReactOS entry */
102  // DPRINT(" An installation '%S' of unsupported type '%S'\n",
103  // BootEntry->FriendlyName, BootEntry->Version ? BootEntry->Version : L"n/a");
104  DPRINT(" An installation '%S' of unsupported type %lu\n",
105  BootEntry->FriendlyName, BootEntry->OsOptionsLength);
106  /* Continue the enumeration */
107  return STATUS_SUCCESS;
108  }
109 
110  /* BootType is Windows2003, now check OsLoadPath */
111  if (!Options->OsLoadPath || !*Options->OsLoadPath)
112  {
113  /* Certainly not a ReactOS installation */
114  DPRINT1(" A Win2k3 install '%S' without an ARC path?!\n", BootEntry->FriendlyName);
115  /* Continue the enumeration */
116  return STATUS_SUCCESS;
117  }
118 
119  DPRINT(" Found a candidate Win2k3 install '%S' with ARC path '%S'\n",
120  BootEntry->FriendlyName, Options->OsLoadPath);
121  // DPRINT(" Found a Win2k3 install '%S' with ARC path '%S'\n",
122  // BootEntry->FriendlyName, Options->OsLoadPath);
123 
124  // TODO: Normalize the ARC path.
125 
126  /*
127  * Check whether we already have an installation with this ARC path.
128  * If this is the case, stop there.
129  */
130  NtOsInstall = FindExistingNTOSInstall(Data->List, Options->OsLoadPath, NULL);
131  if (NtOsInstall)
132  {
133  DPRINT(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n",
134  NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemArcPath);
135  /* Continue the enumeration */
136  return STATUS_SUCCESS;
137  }
138 
139  /*
140  * Convert the ARC path into an NT path, from which we will deduce
141  * the real disk drive & partition on which the candidate installation
142  * resides, as well verifying whether it is indeed an NTOS installation.
143  */
144  RtlInitEmptyUnicodeString(&SystemRootPath, SystemRoot, sizeof(SystemRoot));
145  if (!ArcPathToNtPath(&SystemRootPath, Options->OsLoadPath, Data->PartList))
146  {
147  DPRINT1("ArcPathToNtPath(%S) failed, skip the installation.\n", Options->OsLoadPath);
148  /* Continue the enumeration */
149  return STATUS_SUCCESS;
150  }
151 
152  DPRINT("ArcPathToNtPath() succeeded: '%S' --> '%wZ'\n",
153  Options->OsLoadPath, &SystemRootPath);
154 
155  /*
156  * Check whether we already have an installation with this NT path.
157  * If this is the case, stop there.
158  */
159  NtOsInstall = FindExistingNTOSInstall(Data->List, NULL /*Options->OsLoadPath*/, &SystemRootPath);
160  if (NtOsInstall)
161  {
162  DPRINT1(" An NTOS installation with name \"%S\" from vendor \"%S\" already exists in SystemRoot '%wZ'\n",
163  NtOsInstall->InstallationName, NtOsInstall->VendorName, &NtOsInstall->SystemNtPath);
164  /* Continue the enumeration */
165  return STATUS_SUCCESS;
166  }
167 
168  DPRINT("EnumerateInstallations: SystemRootPath: '%wZ'\n", &SystemRootPath);
169 
170  /* Check if this is a valid NTOS installation; stop there if it isn't one */
171  RtlInitEmptyUnicodeString(&VendorName, VendorNameBuffer, sizeof(VendorNameBuffer));
172  if (!IsValidNTOSInstallation(&SystemRootPath, &Machine, &VendorName))
173  {
174  /* Continue the enumeration */
175  return STATUS_SUCCESS;
176  }
177 
178  DPRINT("Found a valid NTOS installation in SystemRoot ARC path '%S', NT path '%wZ'\n",
179  Options->OsLoadPath, &SystemRootPath);
180 
181  /* From the NT path, compute the disk, partition and path components */
182  if (NtPathToDiskPartComponents(SystemRootPath.Buffer, &DiskNumber, &PartitionNumber, &PathComponent))
183  {
184  DPRINT("SystemRootPath = '%wZ' points to disk #%d, partition #%d, path '%S'\n",
185  &SystemRootPath, DiskNumber, PartitionNumber, PathComponent);
186 
187  /* Retrieve the corresponding disk and partition */
188  if (!GetDiskOrPartition(Data->PartList, DiskNumber, PartitionNumber, &DiskEntry, &PartEntry))
189  {
190  DPRINT1("GetDiskOrPartition(disk #%d, partition #%d) failed\n",
191  DiskNumber, PartitionNumber);
192  }
193  }
194  else
195  {
196  DPRINT1("NtPathToDiskPartComponents(%wZ) failed\n", &SystemRootPath);
197  }
198 
199  /* Add the discovered NTOS installation into the list */
201  BootEntry->FriendlyName,
202  Machine,
203  VendorName.Buffer, // FIXME: What if it's not NULL-terminated?
204  Options->OsLoadPath,
205  &SystemRootPath, PathComponent,
206  DiskNumber, PartitionNumber, PartEntry);
207 
208  /* Continue the enumeration */
209  return STATUS_SUCCESS;
210 }
211 
212 /*
213  * FindSubStrI(PCWSTR str, PCWSTR strSearch) :
214  * Searches for a sub-string 'strSearch' inside 'str', similarly to what
215  * wcsstr(str, strSearch) does, but ignores the case during the comparisons.
216  */
218 {
219  PCWSTR cp = str;
220  PCWSTR s1, s2;
221 
222  if (!*strSearch)
223  return str;
224 
225  while (*cp)
226  {
227  s1 = cp;
228  s2 = strSearch;
229 
230  while (*s1 && *s2 && (towupper(*s1) == towupper(*s2)))
231  ++s1, ++s2;
232 
233  if (!*s2)
234  return cp;
235 
236  ++cp;
237  }
238 
239  return NULL;
240 }
241 
242 static BOOLEAN
245  IN PCWSTR PathNameToFile,
246  OUT PUSHORT Machine,
247  OUT PUNICODE_STRING VendorName)
248 {
251  HANDLE FileHandle, SectionHandle;
252  // SIZE_T ViewSize;
253  PVOID ViewBase;
254  PIMAGE_NT_HEADERS NtHeader;
255  PVOID VersionBuffer = NULL; // Read-only
256  PVOID pvData = NULL;
257  UINT BufLen = 0;
258 
259  if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
260  return FALSE;
261 
262  *VendorName->Buffer = UNICODE_NULL;
263  VendorName->Length = 0;
264 
265  Status = OpenAndMapFile(RootDirectory, PathNameToFile,
266  &FileHandle, &SectionHandle, &ViewBase,
267  NULL, FALSE);
268  if (!NT_SUCCESS(Status))
269  {
270  DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status);
271  return FALSE; // Status;
272  }
273 
274  /* Make sure it's a valid NT PE file */
275  NtHeader = RtlImageNtHeader(ViewBase);
276  if (!NtHeader)
277  {
278  DPRINT1("File '%S' does not seem to be a valid NT PE file, bail out\n", PathNameToFile);
280  goto UnmapCloseFile;
281  }
282 
283  /* Retrieve the target architecture of this PE module */
284  *Machine = NtHeader->FileHeader.Machine;
285 
286  /*
287  * Search for a valid executable version and vendor.
288  * NOTE: The module is loaded as a data file, it should be marked as such.
289  */
290  Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL);
291  if (!NT_SUCCESS(Status))
292  {
293  DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", PathNameToFile, Status);
294  goto UnmapCloseFile;
295  }
296 
297  Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen);
298  if (NT_SUCCESS(Status))
299  {
300  USHORT wCodePage = 0, wLangID = 0;
302 
303  wCodePage = LOWORD(*(ULONG*)pvData);
304  wLangID = HIWORD(*(ULONG*)pvData);
305 
307  L"StringFileInfo\\%04X%04X\\CompanyName",
308  wCodePage, wLangID);
309 
310  Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen);
311 
312  /* Fixup the Status in case pvData is NULL */
313  if (NT_SUCCESS(Status) && !pvData)
315 
316  if (NT_SUCCESS(Status) /*&& pvData*/)
317  {
318  /* BufLen includes the NULL terminator count */
319  DPRINT("Found version vendor: \"%S\" for file '%S'\n", pvData, PathNameToFile);
320 
321  RtlStringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength,
322  pvData, BufLen * sizeof(WCHAR));
323  VendorName->Length = wcslen(VendorName->Buffer) * sizeof(WCHAR);
324 
325  Success = TRUE;
326  }
327  }
328 
329  if (!NT_SUCCESS(Status))
330  DPRINT("No version vendor found for file '%S'\n", PathNameToFile);
331 
332 UnmapCloseFile:
333  /* Finally, unmap and close the file */
334  UnMapAndCloseFile(FileHandle, SectionHandle, ViewBase);
335 
336  return Success;
337 }
338 
339 //
340 // TODO: Instead of returning TRUE/FALSE, it would be nice to return
341 // a flag indicating:
342 // - whether the installation is actually valid;
343 // - if it's broken or not (aka. needs for repair, or just upgrading).
344 //
345 static BOOLEAN
347  IN HANDLE SystemRootDirectory,
348  OUT PUSHORT Machine OPTIONAL,
349  OUT PUNICODE_STRING VendorName OPTIONAL)
350 {
352  PCWSTR PathName;
353  USHORT i;
354  USHORT LocalMachine;
355  UNICODE_STRING LocalVendorName;
356  WCHAR VendorNameBuffer[MAX_PATH];
357 
358  /* Check for VendorName validity */
359  if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
360  {
361  /* Don't use it, invalidate the pointer */
362  VendorName = NULL;
363  }
364  else
365  {
366  /* Zero it out */
367  *VendorName->Buffer = UNICODE_NULL;
368  VendorName->Length = 0;
369  }
370 
371  /* Check for the existence of \SystemRoot\System32 */
372  PathName = L"System32\\";
373  if (!DoesDirExist(SystemRootDirectory, PathName))
374  {
375  // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status);
376  return FALSE;
377  }
378 
379  /* Check for the existence of \SystemRoot\System32\drivers */
380  PathName = L"System32\\drivers\\";
381  if (!DoesDirExist(SystemRootDirectory, PathName))
382  {
383  // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status);
384  return FALSE;
385  }
386 
387  /* Check for the existence of \SystemRoot\System32\config */
388  PathName = L"System32\\config\\";
389  if (!DoesDirExist(SystemRootDirectory, PathName))
390  {
391  // DPRINT1("Failed to open directory '%S', Status 0x%08lx\n", PathName, Status);
392  return FALSE;
393  }
394 
395 #if 0
396  /*
397  * Check for the existence of SYSTEM and SOFTWARE hives in \SystemRoot\System32\config
398  * (but we don't check here whether they are actually valid).
399  */
400  PathName = L"System32\\config\\SYSTEM";
401  if (!DoesFileExist(SystemRootDirectory, PathName))
402  {
403  // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status);
404  return FALSE;
405  }
406  PathName = L"System32\\config\\SOFTWARE";
407  if (!DoesFileExist(SystemRootDirectory, PathName))
408  {
409  // DPRINT1("Failed to open file '%S', Status 0x%08lx\n", PathName, Status);
410  return FALSE;
411  }
412 #endif
413 
414  RtlInitEmptyUnicodeString(&LocalVendorName, VendorNameBuffer, sizeof(VendorNameBuffer));
415 
416  /* Check for the existence of \SystemRoot\System32\ntoskrnl.exe and retrieves its vendor name */
417  PathName = L"System32\\ntoskrnl.exe";
418  Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName);
419  if (!Success)
420  DPRINT1("Kernel executable '%S' is either not a PE file, or does not have any vendor?\n", PathName);
421 
422  /*
423  * The kernel gives the OS its flavour. If we failed due to the absence of
424  * ntoskrnl.exe this might be due to the fact this particular installation
425  * uses a custom kernel that has a different name, overridden in the boot
426  * parameters. We then rely on the existence of ntdll.dll, which cannot be
427  * renamed on a valid NT system.
428  */
429  if (Success)
430  {
431  for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
432  {
433  Success = !!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]);
434  if (Success)
435  {
436  /* We have found a correct vendor combination */
437  DPRINT("IsValidNTOSInstallation: We've got an NTOS installation from %S !\n", KnownVendors[i]);
438  break;
439  }
440  }
441 
442  /* Return the target architecture */
443  if (Machine)
444  {
445  /* Copy the value and invalidate the pointer */
446  *Machine = LocalMachine;
447  Machine = NULL;
448  }
449 
450  /* Return the vendor name */
451  if (VendorName)
452  {
453  /* Copy the string and invalidate the pointer */
454  RtlCopyUnicodeString(VendorName, &LocalVendorName);
455  VendorName = NULL;
456  }
457  }
458 
459  /* OPTIONAL: Check for the existence of \SystemRoot\System32\ntkrnlpa.exe */
460 
461  /* Check for the existence of \SystemRoot\System32\ntdll.dll and retrieves its vendor name */
462  PathName = L"System32\\ntdll.dll";
463  Success = CheckForValidPEAndVendor(SystemRootDirectory, PathName, &LocalMachine, &LocalVendorName);
464  if (!Success)
465  DPRINT1("User-mode DLL '%S' is either not a PE file, or does not have any vendor?\n", PathName);
466 
467  if (Success)
468  {
469  for (i = 0; i < ARRAYSIZE(KnownVendors); ++i)
470  {
471  if (!!FindSubStrI(LocalVendorName.Buffer, KnownVendors[i]))
472  {
473  /* We have found a correct vendor combination */
474  DPRINT("IsValidNTOSInstallation: The user-mode DLL '%S' is from %S\n", PathName, KnownVendors[i]);
475  break;
476  }
477  }
478 
479  /* Return the target architecture if not already obtained */
480  if (Machine)
481  {
482  /* Copy the value and invalidate the pointer */
483  *Machine = LocalMachine;
484  Machine = NULL;
485  }
486 
487  /* Return the vendor name if not already obtained */
488  if (VendorName)
489  {
490  /* Copy the string and invalidate the pointer */
491  RtlCopyUnicodeString(VendorName, &LocalVendorName);
492  VendorName = NULL;
493  }
494  }
495 
496  return Success;
497 }
498 
499 static BOOLEAN
501  IN PUNICODE_STRING SystemRootPath,
502  OUT PUSHORT Machine,
503  OUT PUNICODE_STRING VendorName OPTIONAL)
504 {
508  HANDLE SystemRootDirectory;
510 
511  /* Open SystemRootPath */
513  SystemRootPath,
515  NULL,
516  NULL);
517  Status = NtOpenFile(&SystemRootDirectory,
520  &IoStatusBlock,
523  if (!NT_SUCCESS(Status))
524  {
525  DPRINT1("Failed to open SystemRoot '%wZ', Status 0x%08lx\n", SystemRootPath, Status);
526  return FALSE;
527  }
528 
529  Success = IsValidNTOSInstallationByHandle(SystemRootDirectory,
530  Machine, VendorName);
531 
532  /* Done! */
533  NtClose(SystemRootDirectory);
534  return Success;
535 }
536 
537 #ifndef NDEBUG
538 static VOID
539 DumpNTOSInstalls(
541 {
543  PNTOS_INSTALLATION NtOsInstall;
544  ULONG NtOsInstallsCount = GetNumberOfListEntries(List);
545 
546  DPRINT("There %s %d installation%s detected:\n",
547  NtOsInstallsCount >= 2 ? "are" : "is",
548  NtOsInstallsCount,
549  NtOsInstallsCount >= 2 ? "s" : "");
550 
552  {
553  NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
554 
555  DPRINT(" On disk #%d, partition #%d: Installation \"%S\" in SystemRoot '%wZ'\n",
556  NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber,
557  NtOsInstall->InstallationName, &NtOsInstall->SystemNtPath);
558  }
559 
560  DPRINT("Done.\n");
561 }
562 #endif
563 
564 static PNTOS_INSTALLATION
567  IN PCWSTR SystemRootArcPath OPTIONAL,
568  IN PUNICODE_STRING SystemRootNtPath OPTIONAL // or PCWSTR ?
569  )
570 {
572  PNTOS_INSTALLATION NtOsInstall;
573  UNICODE_STRING SystemArcPath;
574 
575  /*
576  * We search either via ARC path or NT path.
577  * If both pointers are NULL then we fail straight away.
578  */
579  if (!SystemRootArcPath && !SystemRootNtPath)
580  return NULL;
581 
582  RtlInitUnicodeString(&SystemArcPath, SystemRootArcPath);
583 
585  {
586  NtOsInstall = (PNTOS_INSTALLATION)GetListEntryData(Entry);
587 
588  /*
589  * Note that if both ARC paths are equal, then the corresponding
590  * NT paths must be the same. However, two ARC paths may be different
591  * but resolve into the same NT path.
592  */
593  if ( (SystemRootArcPath &&
594  RtlEqualUnicodeString(&NtOsInstall->SystemArcPath,
595  &SystemArcPath, TRUE)) ||
596  (SystemRootNtPath &&
597  RtlEqualUnicodeString(&NtOsInstall->SystemNtPath,
598  SystemRootNtPath, TRUE)) )
599  {
600  /* Found it! */
601  return NtOsInstall;
602  }
603  }
604 
605  return NULL;
606 }
607 
608 static PNTOS_INSTALLATION
611  IN PCWSTR InstallationName,
612  IN USHORT Machine,
613  IN PCWSTR VendorName,
614  IN PCWSTR SystemRootArcPath,
615  IN PUNICODE_STRING SystemRootNtPath, // or PCWSTR ?
616  IN PCWSTR PathComponent, // Pointer inside SystemRootNtPath buffer
617  IN ULONG DiskNumber,
619  IN PPARTENTRY PartEntry OPTIONAL)
620 {
621  PNTOS_INSTALLATION NtOsInstall;
622  SIZE_T ArcPathLength, NtPathLength;
623 
624  /* Is there already any installation with these settings? */
625  NtOsInstall = FindExistingNTOSInstall(List, SystemRootArcPath, SystemRootNtPath);
626  if (NtOsInstall)
627  {
628  DPRINT1("An NTOS installation with name \"%S\" from vendor \"%S\" already exists on disk #%d, partition #%d, in SystemRoot '%wZ'\n",
629  NtOsInstall->InstallationName, NtOsInstall->VendorName,
630  NtOsInstall->DiskNumber, NtOsInstall->PartitionNumber, &NtOsInstall->SystemNtPath);
631  //
632  // NOTE: We may use its "IsDefault" attribute, and only keep the entries that have IsDefault == TRUE...
633  // Setting IsDefault to TRUE would imply searching for the "Default" entry in the loader configuration file.
634  //
635  return NtOsInstall;
636  }
637 
638  ArcPathLength = (wcslen(SystemRootArcPath) + 1) * sizeof(WCHAR);
639  // NtPathLength = ROUND_UP(SystemRootNtPath->Length + sizeof(UNICODE_NULL), sizeof(WCHAR));
640  NtPathLength = SystemRootNtPath->Length + sizeof(UNICODE_NULL);
641 
642  /* None was found, so add a new one */
644  sizeof(*NtOsInstall) +
645  ArcPathLength + NtPathLength);
646  if (!NtOsInstall)
647  return NULL;
648 
649  NtOsInstall->DiskNumber = DiskNumber;
650  NtOsInstall->PartitionNumber = PartitionNumber;
651  NtOsInstall->PartEntry = PartEntry;
652 
653  NtOsInstall->Machine = Machine;
654 
655  RtlInitEmptyUnicodeString(&NtOsInstall->SystemArcPath,
656  (PWCHAR)(NtOsInstall + 1),
657  ArcPathLength);
658  RtlCopyMemory(NtOsInstall->SystemArcPath.Buffer, SystemRootArcPath, ArcPathLength);
659  NtOsInstall->SystemArcPath.Length = ArcPathLength - sizeof(UNICODE_NULL);
660 
661  RtlInitEmptyUnicodeString(&NtOsInstall->SystemNtPath,
662  (PWCHAR)((ULONG_PTR)(NtOsInstall + 1) + ArcPathLength),
663  NtPathLength);
664  RtlCopyUnicodeString(&NtOsInstall->SystemNtPath, SystemRootNtPath);
665  NtOsInstall->PathComponent = NtOsInstall->SystemNtPath.Buffer +
666  (PathComponent - SystemRootNtPath->Buffer);
667 
668  RtlStringCchCopyW(NtOsInstall->InstallationName,
669  ARRAYSIZE(NtOsInstall->InstallationName),
670  InstallationName);
671 
672  RtlStringCchCopyW(NtOsInstall->VendorName,
673  ARRAYSIZE(NtOsInstall->VendorName),
674  VendorName);
675 
676  AppendGenericListEntry(List, NtOsInstall, FALSE);
677 
678  return NtOsInstall;
679 }
680 
681 static VOID
684  IN PPARTLIST PartList,
685  IN PPARTENTRY PartEntry)
686 {
688  ULONG DiskNumber = PartEntry->DiskEntry->DiskNumber;
689  ULONG PartitionNumber = PartEntry->PartitionNumber;
690  HANDLE PartitionDirectoryHandle;
693  UNICODE_STRING PartitionRootPath;
695  PVOID BootStoreHandle;
697  ULONG Version;
698  WCHAR PathBuffer[MAX_PATH];
699 
700  ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0);
701 
702  /* Set PartitionRootPath */
703  RtlStringCchPrintfW(PathBuffer, ARRAYSIZE(PathBuffer),
704  L"\\Device\\Harddisk%lu\\Partition%lu\\",
705  DiskNumber, PartitionNumber);
706  RtlInitUnicodeString(&PartitionRootPath, PathBuffer);
707  DPRINT("FindNTOSInstallations: PartitionRootPath: '%wZ'\n", &PartitionRootPath);
708 
709  /* Open the partition */
711  &PartitionRootPath,
713  NULL,
714  NULL);
715  Status = NtOpenFile(&PartitionDirectoryHandle,
718  &IoStatusBlock,
721  if (!NT_SUCCESS(Status))
722  {
723  DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionRootPath, Status);
724  return;
725  }
726 
727  Data.List = List;
728  Data.PartList = PartList;
729 
730  /* Try to see whether we recognize some NT boot loaders */
731  for (Type = FreeLdr; Type < BldrTypeMax; ++Type)
732  {
733  Status = FindBootStore(PartitionDirectoryHandle, Type, &Version);
734  if (!NT_SUCCESS(Status))
735  {
736  /* The loader does not exist, continue with another one */
737  DPRINT("Loader type '%d' does not exist, or an error happened (Status 0x%08lx), continue with another one...\n",
738  Type, Status);
739  continue;
740  }
741 
742  /* The loader exists, try to enumerate its boot entries */
743  DPRINT("Analyze the OS installations for loader type '%d' in disk #%d, partition #%d\n",
744  Type, DiskNumber, PartitionNumber);
745 
746  Status = OpenBootStoreByHandle(&BootStoreHandle, PartitionDirectoryHandle, Type, FALSE);
747  if (!NT_SUCCESS(Status))
748  {
749  DPRINT1("Could not open the NTOS boot store of type '%d' (Status 0x%08lx), continue with another one...\n",
750  Type, Status);
751  continue;
752  }
754  CloseBootStore(BootStoreHandle);
755  }
756 
757  /* Close the partition */
758  NtClose(PartitionDirectoryHandle);
759 }
760 
761 // static
764  IN PPARTENTRY PartEntry)
765 {
766  if (!PartEntry)
767  return FALSE;
768 
769  return PartEntry->IsPartitioned &&
770  !IsContainerPartition(PartEntry->PartitionType) /* alternatively: PartEntry->PartitionNumber != 0 */ &&
771  !PartEntry->New &&
772  (PartEntry->FormatState == Preformatted /* || PartEntry->FormatState == Formatted */);
773 }
774 
775 // EnumerateNTOSInstallations
778  IN PPARTLIST PartList)
779 {
781  PLIST_ENTRY Entry, Entry2;
782  PDISKENTRY DiskEntry;
783  PPARTENTRY PartEntry;
784 
786  if (List == NULL)
787  return NULL;
788 
789  /* Loop each available disk ... */
790  Entry = PartList->DiskListHead.Flink;
791  while (Entry != &PartList->DiskListHead)
792  {
793  DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
794  Entry = Entry->Flink;
795 
796  DPRINT("Disk #%d\n", DiskEntry->DiskNumber);
797 
798  /* ... and for each disk, loop each available partition */
799 
800  /* First, the primary partitions */
801  Entry2 = DiskEntry->PrimaryPartListHead.Flink;
802  while (Entry2 != &DiskEntry->PrimaryPartListHead)
803  {
804  PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
805  Entry2 = Entry2->Flink;
806 
807  ASSERT(PartEntry->DiskEntry == DiskEntry);
808 
809  DPRINT(" Primary Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
810  PartEntry->PartitionNumber, PartEntry->PartitionIndex,
811  PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
812  PartEntry->IsPartitioned ? "TRUE" : "FALSE",
813  PartEntry->New ? "Yes" : "No",
814  PartEntry->AutoCreate ? "Yes" : "No",
815  PartEntry->FormatState,
816  ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
817 
818  if (ShouldICheckThisPartition(PartEntry))
819  FindNTOSInstallations(List, PartList, PartEntry);
820  }
821 
822  /* Then, the logical partitions (present in the extended partition) */
823  Entry2 = DiskEntry->LogicalPartListHead.Flink;
824  while (Entry2 != &DiskEntry->LogicalPartListHead)
825  {
826  PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
827  Entry2 = Entry2->Flink;
828 
829  ASSERT(PartEntry->DiskEntry == DiskEntry);
830 
831  DPRINT(" Logical Partition #%d, index %d - Type 0x%02x, IsLogical = %s, IsPartitioned = %s, IsNew = %s, AutoCreate = %s, FormatState = %lu -- Should I check it? %s\n",
832  PartEntry->PartitionNumber, PartEntry->PartitionIndex,
833  PartEntry->PartitionType, PartEntry->LogicalPartition ? "TRUE" : "FALSE",
834  PartEntry->IsPartitioned ? "TRUE" : "FALSE",
835  PartEntry->New ? "Yes" : "No",
836  PartEntry->AutoCreate ? "Yes" : "No",
837  PartEntry->FormatState,
838  ShouldICheckThisPartition(PartEntry) ? "YES!" : "NO!");
839 
840  if (ShouldICheckThisPartition(PartEntry))
841  FindNTOSInstallations(List, PartList, PartEntry);
842  }
843  }
844 
845 #ifndef NDEBUG
846  /**** Debugging: List all the collected installations ****/
847  DumpNTOSInstalls(List);
848 #endif
849 
850  return List;
851 }
852 
853 /* EOF */
LIST_ENTRY PrimaryPartListHead
Definition: partlist.h:125
#define NTOS_OPTIONS_SIGNATURE
Definition: bldrsup.h:98
IN PUNICODE_STRING IN POBJECT_ATTRIBUTES ObjectAttributes
Definition: conport.c:35
#define RTL_FIELD_SIZE(type, field)
Definition: kdb_expr.c:84
const uint16_t * PCWSTR
Definition: typedefs.h:55
Definition: genlist.h:10
#define IN
Definition: typedefs.h:38
ULONG PartitionNumber
Definition: partlist.h:49
static BOOLEAN IsValidNTOSInstallation(IN PUNICODE_STRING SystemRootPath, OUT PUSHORT Machine OPTIONAL, OUT PUNICODE_STRING VendorName OPTIONAL)
Definition: osdetect.c:500
struct S2 s2
#define TRUE
Definition: types.h:120
NTSYSAPI VOID NTAPI RtlCopyMemory(VOID UNALIGNED *Destination, CONST VOID UNALIGNED *Source, ULONG Length)
PGENERIC_LIST_ENTRY GetFirstListEntry(IN PGENERIC_LIST List)
Definition: genlist.c:104
IN PVOID IN PVOID IN USHORT Version
Definition: pci.h:359
ULONG GetNumberOfListEntries(IN PGENERIC_LIST List)
Definition: genlist.c:140
static BOOLEAN CheckForValidPEAndVendor(IN HANDLE RootDirectory OPTIONAL, IN PCWSTR PathNameToFile, OUT PUSHORT Machine, OUT PUNICODE_STRING VendorName)
Definition: osdetect.c:243
Type
Definition: Type.h:6
WCHAR RootDirectory[MAX_PATH]
Definition: format.c:74
struct _Entry Entry
Definition: kefuncs.h:640
#define BufLen
Definition: fatfs.h:167
WCHAR VendorName[MAX_PATH]
Definition: osdetect.h:27
#define FILE_DIRECTORY_FILE
Definition: constants.h:491
LONG NTSTATUS
Definition: precomp.h:26
BOOLEAN ArcPathToNtPath(OUT PUNICODE_STRING NtPath, IN PCWSTR ArcPath, IN PPARTLIST PartList OPTIONAL)
Definition: arcname.c:797
ULONG DiskNumber
Definition: partlist.h:105
#define ARRAYSIZE(array)
Definition: filtermapper.c:47
NTSTATUS EnumerateBootStoreEntries(IN PVOID Handle, IN PENUM_BOOT_ENTRIES_ROUTINE EnumBootEntriesRoutine, IN PVOID Parameter OPTIONAL)
Definition: bldrsup.c:1559
#define IsContainerPartition(PartitionType)
Definition: ntdddisk.h:245
uint16_t * PWCHAR
Definition: typedefs.h:54
#define FILE_SHARE_WRITE
Definition: nt_native.h:681
_In_ PVOID Parameter
Definition: ldrtypes.h:240
_In_ ULONG _In_ ULONG PartitionNumber
Definition: iofuncs.h:2056
enum OPTION_FLAGS Options
Definition: stats.c:44
NTSTATUS CloseBootStore(IN PVOID Handle)
Definition: bldrsup.c:827
NTSTATUS NtVerQueryValue(IN const VOID *pBlock, IN PCWSTR lpSubBlock, OUT PVOID *lplpBuffer, OUT PUINT puLen)
Definition: ntverrsrc.c:174
#define FILE_SHARE_READ
Definition: compat.h:125
NTSYSAPI VOID NTAPI RtlCopyUnicodeString(PUNICODE_STRING DestinationString, PUNICODE_STRING SourceString)
static VOID FindNTOSInstallations(IN OUT PGENERIC_LIST List, IN PPARTLIST PartList, IN PPARTENTRY PartEntry)
Definition: osdetect.c:682
PPARTENTRY PartEntry
Definition: osdetect.h:25
uint32_t ULONG_PTR
Definition: typedefs.h:63
#define FILE_TRAVERSE
Definition: nt_native.h:643
static PNTOS_INSTALLATION AddNTOSInstallation(IN PGENERIC_LIST List, IN PCWSTR InstallationName, IN USHORT Machine, IN PCWSTR VendorName, IN PCWSTR SystemRootArcPath, IN PUNICODE_STRING SystemRootNtPath, IN PCWSTR PathComponent, IN ULONG DiskNumber, IN ULONG PartitionNumber, IN PPARTENTRY PartEntry OPTIONAL)
Definition: osdetect.c:609
HANDLE FileHandle
Definition: stats.c:38
IN PPARTLIST PartList
Definition: osdetect.c:63
BOOLEAN LogicalPartition
Definition: partlist.h:57
GLsizei GLenum const GLvoid GLsizei GLenum GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLint GLint GLint GLshort GLshort GLshort GLubyte GLubyte GLubyte GLuint GLuint GLuint GLushort GLushort GLushort GLbyte GLbyte GLbyte GLbyte GLdouble GLdouble GLdouble GLdouble GLfloat GLfloat GLfloat GLfloat GLint GLint GLint GLint GLshort GLshort GLshort GLshort GLubyte GLubyte GLubyte GLubyte GLuint GLuint GLuint GLuint GLushort GLushort GLushort GLushort GLboolean const GLdouble const GLfloat const GLint const GLshort const GLbyte const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLdouble const GLfloat const GLfloat const GLint const GLint const GLshort const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort const GLdouble const GLfloat const GLint const GLshort GLenum GLenum GLenum GLfloat GLenum GLint GLenum GLenum GLenum GLfloat GLenum GLenum GLint GLenum GLfloat GLenum GLint GLint GLushort GLenum GLenum GLfloat GLenum GLenum GLint GLfloat const GLubyte GLenum GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLint GLint GLsizei GLsizei GLint GLenum GLenum const GLvoid GLenum GLenum const GLfloat GLenum GLenum const GLint GLenum GLenum const GLdouble GLenum GLenum const GLfloat GLenum GLenum const GLint GLsizei GLuint GLfloat GLuint GLbitfield GLfloat GLint GLuint GLboolean GLenum GLfloat GLenum GLbitfield GLenum GLfloat GLfloat GLint GLint const GLfloat GLenum GLfloat GLfloat GLint GLint GLfloat GLfloat GLint GLint const GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat GLint GLfloat GLfloat const GLdouble const GLfloat const GLdouble const GLfloat GLint i
Definition: glfuncs.h:248
NTSTATUS NtGetVersionResource(IN PVOID BaseAddress, OUT PVOID *Resource, OUT PULONG ResourceSize OPTIONAL)
Definition: ntverrsrc.c:26
NTSTATUS(* NTAPI)(IN PFILE_FULL_EA_INFORMATION EaBuffer, IN ULONG EaLength, OUT PULONG ErrorOffset)
Definition: IoEaTest.cpp:117
#define UNICODE_NULL
FORMATSTATE FormatState
Definition: partlist.h:55
PVOID GetListEntryData(IN PGENERIC_LIST_ENTRY Entry)
Definition: genlist.c:126
NTSTRSAFEAPI RtlStringCbCopyNW(_Out_writes_bytes_(cbDest) NTSTRSAFE_PWSTR pszDest, _In_ size_t cbDest, _In_reads_bytes_(cbToCopy) STRSAFE_LPCWSTR pszSrc, _In_ size_t cbToCopy)
Definition: ntstrsafe.h:411
PGENERIC_LIST_ENTRY GetNextListEntry(IN PGENERIC_LIST_ENTRY Entry)
Definition: genlist.c:114
const WCHAR * str
unsigned char BOOLEAN
NTSTATUS FindBootStore(IN HANDLE PartitionDirectoryHandle, IN BOOT_STORE_TYPE Type, OUT PULONG VersionNumber OPTIONAL)
Definition: bldrsup.c:137
_In_ ULONG _In_opt_ PVOID pvData
Definition: winddi.h:3748
smooth NULL
Definition: ftsmooth.c:416
#define FORCEINLINE
Definition: ntbasedef.h:221
_In_ LPGUID _In_ PVOID Data
Definition: classpnp.h:778
void DPRINT(...)
Definition: polytest.cpp:61
#define DoesFileExist(RootDirectory, FileName)
Definition: filesup.h:77
static __inline NTSTATUS RtlStringCchPrintfW(_Out_writes_(cchDest) _Always_(_Post_z_) NTSTRSAFE_PWSTR pszDest, _In_ size_t cchDest, _In_ _Printf_format_string_ NTSTRSAFE_PCWSTR pszFormat,...)
Definition: ntstrsafe.h:1064
IMAGE_FILE_HEADER FileHeader
Definition: ntddk_ex.h:183
PFLT_MESSAGE_WAITER_QUEUE CONTAINING_RECORD(Csq, DEVICE_EXTENSION, IrpQueue)) -> WaiterQ.mLock) _IRQL_raises_(DISPATCH_LEVEL) VOID NTAPI FltpAcquireMessageWaiterLock(_In_ PIO_CSQ Csq, _Out_ PKIRQL Irql)
Definition: Messaging.c:560
BOOLEAN AutoCreate
Definition: partlist.h:68
PVOID NTAPI RtlAllocateHeap(IN PVOID HeapHandle, IN ULONG Flags, IN SIZE_T Size)
Definition: heap.c:585
struct _ENUM_INSTALLS_DATA ENUM_INSTALLS_DATA
PGENERIC_LIST CreateGenericList(VOID)
Definition: genlist.c:20
struct _LIST_ENTRY * Flink
Definition: typedefs.h:119
NTSYSAPI NTSTATUS NTAPI NtOpenFile(OUT PHANDLE phFile, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK pIoStatusBlock, IN ULONG ShareMode, IN ULONG OpenMode)
Definition: file.c:3951
BOOLEAN NtPathToDiskPartComponents(IN PCWSTR NtPath, OUT PULONG pDiskNumber, OUT PULONG pPartNumber, OUT PCWSTR *PathComponent OPTIONAL)
Definition: filesup.c:771
#define STATUS_NOT_FOUND
Definition: shellext.h:67
#define FILE_LIST_DIRECTORY
Definition: nt_native.h:629
UCHAR PartitionType
Definition: partlist.h:46
#define STATUS_INVALID_IMAGE_FORMAT
Definition: ntstatus.h:345
struct _DISKENTRY * DiskEntry
Definition: partlist.h:39
LIST_ENTRY List
Definition: psmgr.c:57
static const WCHAR SystemRoot[]
Definition: reg.c:38
if(!(yy_init))
Definition: macro.lex.yy.c:714
PCWSTR FindSubStrI(PCWSTR str, PCWSTR strSearch)
Definition: osdetect.c:217
__wchar_t WCHAR
Definition: xmlstorage.h:180
#define NT_SUCCESS(StatCode)
Definition: apphelp.c:32
struct S1 s1
#define MAX_PATH
Definition: compat.h:26
WCHAR InstallationName[MAX_PATH]
Definition: osdetect.h:26
static PNTOS_INSTALLATION FindExistingNTOSInstall(IN PGENERIC_LIST List, IN PCWSTR SystemRootArcPath OPTIONAL, IN PUNICODE_STRING SystemRootNtPath OPTIONAL)
Definition: osdetect.c:565
NTSTATUS NTAPI NtClose(IN HANDLE Handle)
Definition: obhandle.c:3399
#define OBJ_CASE_INSENSITIVE
Definition: winternl.h:228
ASSERT((InvokeOnSuccess||InvokeOnError||InvokeOnCancel) ?(CompletionRoutine !=NULL) :TRUE)
BOOLEAN AppendGenericListEntry(IN OUT PGENERIC_LIST List, IN PVOID Data, IN BOOLEAN Current)
Definition: genlist.c:62
#define DoesDirExist(RootDirectory, DirName)
Definition: filesup.h:74
static const WCHAR L[]
Definition: oid.c:1250
Definition: typedefs.h:117
#define SYNCHRONIZE
Definition: nt_native.h:61
static NTSTATUS NTAPI EnumerateInstallations(IN BOOT_STORE_TYPE Type, IN PBOOT_STORE_ENTRY BootEntry, IN PVOID Parameter OPTIONAL)
Definition: osdetect.c:70
struct _NTOS_INSTALLATION * PNTOS_INSTALLATION
HANDLE ProcessHeap
Definition: servman.c:15
BOOLEAN New
Definition: partlist.h:65
Status
Definition: gdiplustypes.h:24
ULONG_PTR SIZE_T
Definition: typedefs.h:78
enum _BOOT_STORE_TYPE BOOT_STORE_TYPE
PCWSTR PathComponent
Definition: osdetect.h:22
NTSTRSAFEAPI RtlStringCchCopyW(_Out_writes_(cchDest) _Always_(_Post_z_) NTSTRSAFE_PWSTR pszDest, _In_ size_t cchDest, _In_ NTSTRSAFE_PCWSTR pszSrc)
Definition: ntstrsafe.h:135
#define UnMapAndCloseFile(FileHandle, SectionHandle, BaseAddress)
Definition: filesup.h:108
unsigned short USHORT
Definition: pedump.c:61
UNICODE_STRING SystemArcPath
Definition: osdetect.h:20
BOOLEAN GetDiskOrPartition(IN PPARTLIST List, IN ULONG DiskNumber, IN ULONG PartitionNumber OPTIONAL, OUT PDISKENTRY *pDiskEntry, OUT PPARTENTRY *pPartEntry OPTIONAL)
Definition: partlist.c:2129
static const PCWSTR KnownVendors[]
Definition: osdetect.c:29
static OUT PIO_STATUS_BLOCK IoStatusBlock
Definition: pipe.c:75
unsigned int UINT
Definition: ndis.h:50
#define HEAP_ZERO_MEMORY
Definition: compat.h:123
ULONG PartitionNumber
Definition: osdetect.h:24
FORCEINLINE BOOLEAN ShouldICheckThisPartition(IN PPARTENTRY PartEntry)
Definition: osdetect.c:763
#define DPRINT1
Definition: precomp.h:8
#define RtlImageNtHeader
Definition: compat.h:457
LIST_ENTRY LogicalPartListHead
Definition: partlist.h:126
#define FILE_SYNCHRONOUS_IO_NONALERT
Definition: from_kernel.h:31
struct _NTOS_OPTIONS * PNTOS_OPTIONS
POINT cp
Definition: magnifier.c:59
#define OUT
Definition: typedefs.h:39
#define HIWORD(l)
Definition: typedefs.h:246
unsigned int ULONG
Definition: retypes.h:1
NTSYSAPI VOID NTAPI RtlInitUnicodeString(PUNICODE_STRING DestinationString, PCWSTR SourceString)
#define InitializeObjectAttributes(p, n, a, r, s)
Definition: reg.c:106
NTSTATUS OpenAndMapFile(IN HANDLE RootDirectory OPTIONAL, IN PCWSTR PathNameToFile, OUT PHANDLE FileHandle, OUT PHANDLE SectionHandle, OUT PVOID *BaseAddress, OUT PULONG FileSize OPTIONAL, IN BOOLEAN ReadWriteAccess)
Definition: filesup.c:858
#define towupper(c)
Definition: wctype.h:99
#define VENDOR_MICROSOFT
Definition: osdetect.h:13
IN OUT PGENERIC_LIST List
Definition: osdetect.c:62
return STATUS_SUCCESS
Definition: btrfs.c:2777
NTSTATUS OpenBootStoreByHandle(OUT PVOID *Handle, IN HANDLE PartitionDirectoryHandle, IN BOOT_STORE_TYPE Type, IN BOOLEAN CreateNew)
Definition: bldrsup.c:737
static const WCHAR Signature[]
Definition: parser.c:141
struct _ENUM_INSTALLS_DATA * PENUM_INSTALLS_DATA
NTSYSAPI BOOLEAN NTAPI RtlEqualUnicodeString(PUNICODE_STRING String1, PUNICODE_STRING String2, BOOLEAN CaseInSensitive)
#define LOWORD(l)
Definition: pedump.c:82
PGENERIC_LIST CreateNTOSInstallationsList(IN PPARTLIST PartList)
Definition: osdetect.c:777
size_t __cdecl wcslen(_In_z_ const wchar_t *_Str)
static BOOLEAN IsValidNTOSInstallationByHandle(IN HANDLE SystemRootDirectory, OUT PUSHORT Machine OPTIONAL, OUT PUNICODE_STRING VendorName OPTIONAL)
Definition: osdetect.c:346
unsigned short * PUSHORT
Definition: retypes.h:2
Definition: bldrsup.h:62
base of all file and directory entries
Definition: entries.h:82
#define RtlCompareMemory(s1, s2, l)
Definition: env_spec_w32.h:465
BOOLEAN IsPartitioned
Definition: partlist.h:60
#define VENDOR_REACTOS
Definition: osdetect.h:12
ULONG PartitionIndex
Definition: partlist.h:50
PULONG MinorVersion OPTIONAL
Definition: CrossNt.h:68
UNICODE_STRING SystemNtPath
Definition: osdetect.h:21