2 * COPYRIGHT: See COPYING.ARM in the top level directory
3 * PROJECT: ReactOS UEFI Boot Library
4 * FILE: boot/environ/lib/misc/bootreg.c
5 * PURPOSE: Boot Library Boot Registry Wrapper for CMLIB
6 * PROGRAMMER: Alex Ionescu (alex.ionescu@reactos.org)
9 /* INCLUDES ******************************************************************/
14 /* DEFINITIONS ***************************************************************/
16 #define BI_FLUSH_HIVE 0x01
17 #define BI_HIVE_WRITEABLE 0x02
19 /* DATA STRUCTURES ***********************************************************/
21 typedef struct _BI_KEY_HIVE
23 PHBASE_BLOCK BaseBlock
;
25 PBL_FILE_PATH_DESCRIPTOR FilePath
;
29 PCM_KEY_NODE RootNode
;
30 } BI_KEY_HIVE
, *PBI_KEY_HIVE
;
32 typedef struct _BI_KEY_OBJECT
38 } BI_KEY_OBJECT
, *PBI_KEY_OBJECT
;
40 /* GLOBALS *******************************************************************/
42 BOOLEAN BiHiveHashLibraryInitialized
;
43 ULONGLONG HvSymcryptSeed
;
45 /* FUNCTIONS *****************************************************************/
48 HvIsInPlaceBaseBlockValid (
49 _In_ PHBASE_BLOCK BaseBlock
52 ULONG HiveLength
, HeaderSum
;
58 /* Check for incorrect signature, type, version, or format */
59 if ((BaseBlock
->Signature
== 'fger') &&
60 (BaseBlock
->Type
== 0) &&
61 (BaseBlock
->Major
<= 1) &&
62 (BaseBlock
->Minor
<= 5) &&
63 (BaseBlock
->Minor
>= 3) &&
64 (BaseBlock
->Format
== 1))
66 /* Check for invalid hive size */
67 HiveLength
= BaseBlock
->Length
;
70 /* Check for misaligned or too large hive size */
71 if (!(HiveLength
& 0xFFF) && HiveLength
<= 0x7FFFE000)
73 /* Check for invalid header checksum */
74 HeaderSum
= HvpHiveHeaderChecksum(BaseBlock
);
75 if (HeaderSum
== BaseBlock
->CheckSum
)
96 UNREFERENCED_PARAMETER(Paged
);
97 UNREFERENCED_PARAMETER(Tag
);
99 /* Call the heap allocator */
100 return BlMmAllocateHeap(Size
);
110 UNREFERENCED_PARAMETER(Quota
);
112 /* Call the heap allocator */
118 _In_ HANDLE KeyHandle
121 PBI_KEY_OBJECT KeyObject
;
123 /* Get the key object */
124 KeyObject
= (PBI_KEY_OBJECT
)KeyHandle
;
126 /* Drop a reference on the parent hive */
127 --KeyObject
->KeyHive
->ReferenceCount
;
132 _In_ HANDLE KeyHandle
135 /* Not yet implemented */
136 EfiPrintf(L
"NO reg flush\r\n");
142 _In_ HANDLE KeyHandle
145 PBI_KEY_HIVE KeyHive
;
146 PBI_KEY_OBJECT KeyObject
;
148 /* Get the key object and hive */
149 KeyObject
= (PBI_KEY_OBJECT
)KeyHandle
;
150 KeyHive
= KeyObject
->KeyHive
;
152 /* Check if we have a hive, or name, or key node */
153 if ((KeyHive
) || (KeyObject
->KeyNode
) || (KeyObject
->KeyName
))
155 /* Drop a reference, see if it's the last one */
156 BiDereferenceHive(KeyHandle
);
157 if (!KeyHive
->ReferenceCount
)
159 /* Check if we should flush it */
160 if (KeyHive
->Flags
& BI_FLUSH_HIVE
)
162 BiFlushHive(KeyHandle
);
166 MmPapFreePages(KeyHive
->BaseBlock
, BL_MM_INCLUDE_MAPPED_ALLOCATED
);
168 /* Free the hive and hive path */
169 BlMmFreeHeap(KeyHive
->FilePath
);
170 BlMmFreeHeap(KeyHive
);
173 /* Check if a key name is present */
174 if (KeyObject
->KeyName
)
177 BlMmFreeHeap(KeyObject
->KeyName
);
181 /* Free the object */
182 BlMmFreeHeap(KeyObject
);
187 _In_ HANDLE ParentHandle
,
192 PBI_KEY_OBJECT ParentKey
, NewKey
;
193 PBI_KEY_HIVE ParentHive
;
195 SIZE_T NameLength
, SubNameLength
, NameBytes
;
196 PWCHAR NameStart
, NameBuffer
;
197 UNICODE_STRING KeyString
;
200 PCM_KEY_NODE ParentNode
;
202 /* Convert from a handle to our key object */
203 ParentKey
= (PBI_KEY_OBJECT
)ParentHandle
;
205 /* Extract the hive and node information */
206 ParentHive
= ParentKey
->KeyHive
;
207 ParentNode
= ParentKey
->KeyNode
;
208 Hive
= &ParentKey
->KeyHive
->Hive
.Hive
;
210 /* Initialize variables */
212 Status
= STATUS_SUCCESS
;
215 /* Loop as long as there's still portions of the key name in play */
216 NameLength
= wcslen(KeyName
);
219 /* Find the first path separator */
220 NameStart
= wcschr(KeyName
, OBJ_NAME_PATH_SEPARATOR
);
223 /* Look only at the key before the separator */
224 SubNameLength
= NameStart
- KeyName
;
229 /* No path separator, this is the final leaf key */
230 SubNameLength
= NameLength
;
233 /* Free the name buffer from the previous pass if needed */
236 BlMmFreeHeap(NameBuffer
);
239 /* Allocate a buffer to hold the name of this specific subkey only */
240 NameBytes
= SubNameLength
* sizeof(WCHAR
);
241 NameBuffer
= BlMmAllocateHeap(NameBytes
+ sizeof(UNICODE_NULL
));
244 Status
= STATUS_NO_MEMORY
;
248 /* Copy and null-terminate the name of the subkey */
249 RtlCopyMemory(NameBuffer
, KeyName
, NameBytes
);
250 NameBuffer
[SubNameLength
] = UNICODE_NULL
;
252 /* Convert it into a UNICODE_STRING and try to find it */
253 RtlInitUnicodeString(&KeyString
, NameBuffer
);
254 KeyCell
= CmpFindSubKeyByName(Hive
, ParentNode
, &KeyString
);
255 if (KeyCell
== HCELL_NIL
)
257 Status
= STATUS_OBJECT_NAME_NOT_FOUND
;
261 /* We found it -- get the key node out of it */
262 ParentNode
= (PCM_KEY_NODE
)HvGetCell(Hive
, KeyCell
);
265 Status
= STATUS_REGISTRY_CORRUPT
;
269 /* Update the key name to the next remaining path element */
273 /* Update the length to the remainder of the path */
274 NameLength
+= -1 - SubNameLength
;
278 /* There's nothing left, this was the leaf key */
283 /* Allocate a key object */
284 NewKey
= BlMmAllocateHeap(sizeof(*NewKey
));
287 /* Bail out if we had no memory for it */
288 Status
= STATUS_NO_MEMORY
;
292 /* Fill out the key object data */
293 NewKey
->KeyNode
= ParentNode
;
294 NewKey
->KeyHive
= ParentHive
;
295 NewKey
->KeyName
= NameBuffer
;
296 NewKey
->KeyCell
= KeyCell
;
298 /* Add a reference to the hive */
299 ++ParentHive
->ReferenceCount
;
301 /* Return the object back to the caller */
305 /* If we had a name buffer, free it */
308 BlMmFreeHeap(NameBuffer
);
311 /* Return status of the open operation */
316 BiInitializeAndValidateHive (
317 _In_ PBI_KEY_HIVE Hive
323 /* Make sure the hive is at least the size of a base block */
324 if (Hive
->HiveSize
< sizeof(HBASE_BLOCK
))
326 return STATUS_REGISTRY_CORRUPT
;
329 /* Make sure that the base block accurately describes the size of the hive */
330 HiveSize
= Hive
->BaseBlock
->Length
+ sizeof(HBASE_BLOCK
);
331 if ((HiveSize
< sizeof(HBASE_BLOCK
)) || (HiveSize
> Hive
->HiveSize
))
333 return STATUS_REGISTRY_CORRUPT
;
336 /* Initialize a flat memory hive */
337 RtlZeroMemory(&Hive
->Hive
, sizeof(Hive
->Hive
));
338 Status
= HvInitialize(&Hive
->Hive
.Hive
,
351 if (NT_SUCCESS(Status
))
353 /* Cleanup volatile/old data */
354 CmPrepareHive(&Hive
->Hive
.Hive
); // CmCheckRegistry
355 Status
= STATUS_SUCCESS
;
358 /* Return the final status */
364 _In_ PBL_FILE_PATH_DESCRIPTOR FilePath
,
365 _Out_ PHANDLE HiveHandle
369 PHBASE_BLOCK BaseBlock
, NewBaseBlock
;
370 PBI_KEY_OBJECT KeyObject
;
371 PBI_KEY_HIVE BcdHive
;
372 PBL_DEVICE_DESCRIPTOR BcdDevice
;
373 ULONG PathLength
, DeviceLength
, HiveSize
, HiveLength
, NewHiveSize
;
374 PWCHAR HiveName
, LogName
;
375 BOOLEAN HaveWriteAccess
;
379 UNICODE_STRING KeyString
;
380 PCM_KEY_NODE RootNode
;
381 HCELL_INDEX CellIndex
;
383 /* Initialize variables */
391 /* Initialize the crypto seed */
392 if (!BiHiveHashLibraryInitialized
)
394 HvSymcryptSeed
= 0x82EF4D887A4E55C5;
395 BiHiveHashLibraryInitialized
= TRUE
;
398 /* Extract and validate the input path */
399 BcdDevice
= (PBL_DEVICE_DESCRIPTOR
)&FilePath
->Path
;
400 PathLength
= FilePath
->Length
;
401 DeviceLength
= BcdDevice
->Size
;
402 HiveName
= (PWCHAR
)((ULONG_PTR
)BcdDevice
+ BcdDevice
->Size
);
403 if (PathLength
<= DeviceLength
)
405 /* Doesn't make sense, bail out */
406 Status
= STATUS_INVALID_PARAMETER
;
410 /* Attempt to open the underlying device for RW access */
411 HaveWriteAccess
= TRUE
;
412 Status
= BlpDeviceOpen(BcdDevice
,
413 BL_DEVICE_READ_ACCESS
| BL_DEVICE_WRITE_ACCESS
,
416 if (!NT_SUCCESS(Status
))
418 /* Try for RO access instead */
419 HaveWriteAccess
= FALSE
;
420 Status
= BlpDeviceOpen(BcdDevice
, BL_DEVICE_READ_ACCESS
, 0, &DeviceId
);
421 if (!NT_SUCCESS(Status
))
423 /* No access at all -- bail out */
428 /* Now try to load the hive on disk */
429 Status
= BlImgLoadImageWithProgress2(DeviceId
,
438 if (!NT_SUCCESS(Status
))
440 EfiPrintf(L
"Hive read failure: % lx\r\n", Status
);
444 /* Allocate a hive structure */
445 BcdHive
= BlMmAllocateHeap(sizeof(*BcdHive
));
448 Status
= STATUS_NO_MEMORY
;
453 RtlZeroMemory(BcdHive
, sizeof(*BcdHive
));
454 BcdHive
->BaseBlock
= BaseBlock
;
455 BcdHive
->HiveSize
= HiveSize
;
458 BcdHive
->Flags
|= BI_HIVE_WRITEABLE
;
461 /* Make sure the hive was at least one bin long */
462 if (HiveSize
< sizeof(*BaseBlock
))
464 Status
= STATUS_REGISTRY_CORRUPT
;
468 /* Make sure the hive contents are at least one bin long */
469 HiveLength
= BaseBlock
->Length
;
470 if (BaseBlock
->Length
< sizeof(*BaseBlock
))
472 Status
= STATUS_REGISTRY_CORRUPT
;
476 /* Validate the initial bin (the base block) */
477 if (!HvIsInPlaceBaseBlockValid(BaseBlock
))
479 EfiPrintf(L
"Recovery not implemented\r\n");
480 Status
= STATUS_REGISTRY_CORRUPT
;
484 /* Check if there's log recovery that needs to happen */
485 if (BaseBlock
->Sequence1
!= BaseBlock
->Sequence2
)
487 EfiPrintf(L
"Log fix not implemented: %lx %lx\r\n");
488 Status
= STATUS_REGISTRY_CORRUPT
;
493 * Check if the whole hive doesn't fit in the buffer.
494 * Note: HiveLength does not include the size of the baseblock itself
496 if (HiveSize
< (HiveLength
+ sizeof(*BaseBlock
)))
498 EfiPrintf(L
"Need bigger hive buffer path\r\n");
500 /* Allocate a slightly bigger buffer */
501 NewHiveSize
= HiveLength
+ sizeof(*BaseBlock
);
502 Status
= MmPapAllocatePagesInRange((PVOID
*)&NewBaseBlock
,
504 NewHiveSize
>> PAGE_SHIFT
,
509 if (!NT_SUCCESS(Status
))
514 /* Copy the current data in there */
515 RtlCopyMemory(NewBaseBlock
, BaseBlock
, HiveSize
);
517 /* Free the old data */
518 MmPapFreePages(BaseBlock
, BL_MM_INCLUDE_MAPPED_ALLOCATED
);
520 /* Update our pointers */
521 BaseBlock
= NewBaseBlock
;
522 HiveSize
= NewHiveSize
;
523 BcdHive
->BaseBlock
= BaseBlock
;
524 BcdHive
->HiveSize
= HiveSize
;
527 /* Check if any log stuff needs to happen */
530 EfiPrintf(L
"Log fix not implemented: %lx %lx\r\n");
531 Status
= STATUS_REGISTRY_CORRUPT
;
535 /* Call Hv to setup the hive library */
536 Status
= BiInitializeAndValidateHive(BcdHive
);
537 if (!NT_SUCCESS(Status
))
542 /* Now get the root node */
543 Hive
= &BcdHive
->Hive
.Hive
;
544 RootNode
= (PCM_KEY_NODE
)HvGetCell(Hive
, Hive
->BaseBlock
->RootCell
);
547 Status
= STATUS_OBJECT_NAME_NOT_FOUND
;
551 /* Find the Objects subkey under it to see if it's a real BCD hive */
552 RtlInitUnicodeString(&KeyString
, L
"Objects");
553 CellIndex
= CmpFindSubKeyByName(Hive
, RootNode
, &KeyString
);
554 if (CellIndex
== HCELL_NIL
)
556 EfiPrintf(L
"No OBJECTS subkey found!\r\n");
557 Status
= STATUS_OBJECT_NAME_NOT_FOUND
;
561 /* This is a valid BCD hive, store its root node here */
562 BcdHive
->RootNode
= RootNode
;
564 /* Allocate a copy of the file path */
565 BcdHive
->FilePath
= BlMmAllocateHeap(FilePath
->Length
);
566 if (!BcdHive
->FilePath
)
568 Status
= STATUS_NO_MEMORY
;
572 /* Make a copy of it */
573 RtlCopyMemory(BcdHive
->FilePath
, FilePath
, FilePath
->Length
);
575 /* Create a key object to describe the rot */
576 KeyObject
= BlMmAllocateHeap(sizeof(*KeyObject
));
579 Status
= STATUS_NO_MEMORY
;
583 /* Fill out the details */
584 KeyObject
->KeyNode
= RootNode
;
585 KeyObject
->KeyHive
= BcdHive
;
586 KeyObject
->KeyName
= NULL
;
587 KeyObject
->KeyCell
= Hive
->BaseBlock
->RootCell
;
589 /* One reference for the key object, plus one lifetime reference */
590 BcdHive
->ReferenceCount
= 2;
592 /* This is the hive handle */
593 *HiveHandle
= KeyObject
;
596 Status
= STATUS_SUCCESS
;
599 /* If we had a log name, free it */
602 BlMmFreeHeap(LogName
);
605 /* If we had logging data, free it */
608 MmPapFreePages(LogData
, BL_MM_INCLUDE_MAPPED_ALLOCATED
);
611 /* Check if this is the failure path */
612 if (!NT_SUCCESS(Status
))
614 /* If we mapped the hive, free it */
617 MmPapFreePages(BaseBlock
, BL_MM_INCLUDE_MAPPED_ALLOCATED
);
620 /* If we opened the device, close it */
623 BlDeviceClose(DeviceId
);
626 /* Did we create a hive object? */
629 /* Free the file path if we made a copy of it */
630 if (BcdHive
->FilePath
)
632 BlMmFreeHeap(BcdHive
->FilePath
);
635 /* Free the hive itself */
636 BlMmFreeHeap(BcdHive
);
639 /* Finally, free the root key object if we created one */
642 BlMmFreeHeap(KeyObject
);
646 /* Return the final status */
652 _In_ HANDLE KeyHandle
,
653 _In_ PWCHAR ValueName
,
656 _Out_ PULONG ValueLength
659 PCM_KEY_NODE KeyNode
;
661 UNICODE_STRING ValueString
;
662 PBI_KEY_OBJECT KeyObject
;
663 PCM_KEY_VALUE KeyValue
;
666 HCELL_INDEX CellIndex
;
667 PCELL_DATA ValueData
;
669 /* Get the key object, node,and hive */
670 KeyObject
= (PBI_KEY_OBJECT
)KeyHandle
;
671 KeyNode
= KeyObject
->KeyNode
;
672 KeyHive
= &KeyObject
->KeyHive
->Hive
.Hive
;
674 /* Find the value cell index in the list of values */
675 RtlInitUnicodeString(&ValueString
, ValueName
);
676 CmpFindNameInList(KeyHive
,
681 if (CellIndex
== HCELL_NIL
)
683 return STATUS_OBJECT_NAME_NOT_FOUND
;
686 /* Get the cell data for it */
687 KeyValue
= (PCM_KEY_VALUE
)HvGetCell(KeyHive
, CellIndex
);
690 return STATUS_REGISTRY_CORRUPT
;
693 /* Make sure the type matches */
694 if (KeyValue
->Type
!= Type
)
696 return STATUS_OBJECT_TYPE_MISMATCH
;
699 /* Now get the data cell */
700 ValueData
= CmpValueToData(KeyHive
, KeyValue
, &Size
);
702 /* Make a copy of it */
703 ValueCopy
= BlMmAllocateHeap(Size
);
706 return STATUS_NO_MEMORY
;
709 /* Copy it in the buffer, and return it and its size */
710 RtlCopyMemory(ValueCopy
, ValueData
, Size
);
713 return STATUS_SUCCESS
;
718 _In_ HANDLE KeyHandle
,
719 _Out_ PWCHAR
** SubKeyList
,
720 _Out_ PULONG SubKeyCount
723 PCM_KEY_NODE KeyNode
, Node
;
724 PBI_KEY_OBJECT KeyObject
;
726 ULONG NameLength
, NewTotalNameLength
, FinalLength
, TotalNameLength
;
728 PWCHAR KeyName
, NameEnd
;
729 HCELL_INDEX CellIndex
;
734 /* Get the key object, node, and hive */
735 KeyObject
= (PBI_KEY_OBJECT
)KeyHandle
;
736 KeyNode
= KeyObject
->KeyNode
;
737 Hive
= &KeyObject
->KeyHive
->Hive
.Hive
;
739 /* Assume it's empty */
743 /* Initialize locals */
748 /* Find the first subkey cell index */
749 CellIndex
= CmpFindSubKeyByNumber(Hive
, KeyNode
, KeyCount
);
750 while (CellIndex
!= HCELL_NIL
)
752 /* Move to the next one */
755 /* Get the cell data for it */
756 Node
= (PCM_KEY_NODE
)HvGetCell(Hive
, CellIndex
);
759 return STATUS_REGISTRY_CORRUPT
;
762 /* Check if the value is compressed */
763 if (Node
->Flags
& KEY_COMP_NAME
)
765 /* Get the compressed name size */
766 NameLength
= CmpCompressedNameSize(Node
->Name
, Node
->NameLength
);
770 /* Get the real size */
771 NameLength
= Node
->NameLength
;
774 /* Add up the new length, protecting against overflow */
775 NewTotalNameLength
= TotalNameLength
+ NameLength
+ sizeof(UNICODE_NULL
);
776 if (NewTotalNameLength
< TotalNameLength
)
778 Status
= STATUS_NAME_TOO_LONG
;
782 /* We're good, use the new length */
783 TotalNameLength
= NewTotalNameLength
;
785 /* Find the next subkey cell index */
786 CellIndex
= CmpFindSubKeyByNumber(Hive
, KeyNode
, KeyCount
);
789 /* Were there no keys? We're done, if so */
792 return STATUS_SUCCESS
;
795 /* Safely compute the size of the array needed */
796 Status
= RtlULongLongToULong(sizeof(PWCHAR
) * KeyCount
, &FinalLength
);
797 if (!NT_SUCCESS(Status
))
802 /* Safely add that to the name length */
803 Status
= RtlULongAdd(TotalNameLength
, FinalLength
, &FinalLength
);
804 if (!NT_SUCCESS(Status
))
809 /* Allocate an array big enough for the names and pointers */
810 SubKeys
= BlMmAllocateHeap(FinalLength
);
813 Status
= STATUS_NO_MEMORY
;
817 /* Go over each key again */
818 NameEnd
= (PWCHAR
)&SubKeys
[KeyCount
];
819 for (i
= 0; i
< KeyCount
; i
++)
821 /* Get the cell index for this subkey */
822 CellIndex
= CmpFindSubKeyByNumber(Hive
, KeyNode
, i
);
823 if (CellIndex
== HCELL_NIL
)
828 /* Get the cell data for it */
829 Node
= HvGetCell(Hive
, CellIndex
);
832 Status
= STATUS_REGISTRY_CORRUPT
;
836 /* Check if the value is compressed */
837 KeyName
= Node
->Name
;
838 if (Node
->Flags
& KEY_COMP_NAME
)
840 /* Get the compressed name size */
841 NameLength
= CmpCompressedNameSize(KeyName
, Node
->NameLength
);
842 CmpCopyCompressedName(NameEnd
, NameLength
, KeyName
, Node
->NameLength
);
846 /* Get the real size */
847 NameLength
= Node
->NameLength
;
848 RtlCopyMemory(NameEnd
, KeyName
, NameLength
);
851 /* Move the name buffer to the next spot, and NULL-terminate */
852 SubKeys
[i
] = NameEnd
;
853 NameEnd
+= (NameLength
/ sizeof(WCHAR
));
854 *NameEnd
= UNICODE_NULL
;
860 /* Check if the subkeys were empty */
863 /* They disappeared in the middle of enumeration */
864 Status
= STATUS_OBJECT_NAME_NOT_FOUND
;
868 /* Return the count and the array of names */
869 *SubKeyList
= SubKeys
;
872 Status
= STATUS_SUCCESS
;
875 /* On the failure path, free the subkeys if any exist */
878 BlMmFreeHeap(SubKeys
);
881 /* All done, return the result */
887 _In_ HANDLE KeyHandle
891 PBI_KEY_OBJECT KeyObject
;
893 ULONG SubKeyCount
, i
;
897 /* Get the key object and hive */
898 KeyObject
= (PBI_KEY_OBJECT
)KeyHandle
;
899 Hive
= &KeyObject
->KeyHive
->Hive
.Hive
;
901 /* Make sure the hive is writeable */
902 if (!(KeyObject
->KeyHive
->Flags
& BI_HIVE_WRITEABLE
))
904 return STATUS_MEDIA_WRITE_PROTECTED
;
907 /* Enumerate all of the subkeys */
908 Status
= BiEnumerateSubKeys(KeyHandle
, &SubKeyList
, &SubKeyCount
);
909 if ((NT_SUCCESS(Status
)) && (SubKeyCount
> 0))
911 /* Loop through each one */
912 for (i
= 0; i
< SubKeyCount
; i
++)
914 /* Open a handle to it */
915 Status
= BiOpenKey(KeyHandle
, SubKeyList
[i
], &SubKeyHandle
);
916 if (NT_SUCCESS(Status
))
918 /* Recursively call us to delete it */
919 Status
= BiDeleteKey(SubKeyHandle
);
920 if (Status
!= STATUS_SUCCESS
)
922 /* Close the key on failure */
923 BiCloseKey(SubKeyHandle
);
929 /* Check if we had a list of subkeys */
933 BlMmFreeHeap(SubKeyList
);
936 /* Delete this key cell */
937 Status
= CmpFreeKeyByCell(Hive
, KeyObject
->KeyCell
, TRUE
);
938 if (NT_SUCCESS(Status
))
940 /* Mark the hive as requiring a flush */
941 KeyObject
->KeyHive
->Flags
|= BI_FLUSH_HIVE
;
942 BiCloseKey(KeyHandle
);