1 /* 2 * PROJECT: Registry manipulation library 3 * LICENSE: GPL - See COPYING in the top level directory 4 * COPYRIGHT: Copyright 2005 Filip Navara <navaraf@reactos.org> 5 * Copyright 2001 - 2005 Eric Kohl 6 */ 7 8 #include "cmlib.h" 9 #define NDEBUG 10 #include <debug.h> 11 12 /** 13 * @name HvpVerifyHiveHeader 14 * 15 * Internal function to verify that a hive header has valid format. 16 */ 17 BOOLEAN CMAPI 18 HvpVerifyHiveHeader( 19 IN PHBASE_BLOCK BaseBlock) 20 { 21 if (BaseBlock->Signature != HV_HBLOCK_SIGNATURE || 22 BaseBlock->Major != HSYS_MAJOR || 23 BaseBlock->Minor < HSYS_MINOR || 24 BaseBlock->Type != HFILE_TYPE_PRIMARY || 25 BaseBlock->Format != HBASE_FORMAT_MEMORY || 26 BaseBlock->Cluster != 1 || 27 BaseBlock->Sequence1 != BaseBlock->Sequence2 || 28 HvpHiveHeaderChecksum(BaseBlock) != BaseBlock->CheckSum) 29 { 30 DPRINT1("Verify Hive Header failed:\n"); 31 DPRINT1(" Signature: 0x%x, expected 0x%x; Major: 0x%x, expected 0x%x\n", 32 BaseBlock->Signature, HV_HBLOCK_SIGNATURE, BaseBlock->Major, HSYS_MAJOR); 33 DPRINT1(" Minor: 0x%x expected to be >= 0x%x; Type: 0x%x, expected 0x%x\n", 34 BaseBlock->Minor, HSYS_MINOR, BaseBlock->Type, HFILE_TYPE_PRIMARY); 35 DPRINT1(" Format: 0x%x, expected 0x%x; Cluster: 0x%x, expected 1\n", 36 BaseBlock->Format, HBASE_FORMAT_MEMORY, BaseBlock->Cluster); 37 DPRINT1(" Sequence: 0x%x, expected 0x%x; Checksum: 0x%x, expected 0x%x\n", 38 BaseBlock->Sequence1, BaseBlock->Sequence2, 39 HvpHiveHeaderChecksum(BaseBlock), BaseBlock->CheckSum); 40 41 return FALSE; 42 } 43 44 return TRUE; 45 } 46 47 /** 48 * @name HvpFreeHiveBins 49 * 50 * Internal function to free all bin storage associated with a hive descriptor. 51 */ 52 VOID CMAPI 53 HvpFreeHiveBins( 54 PHHIVE Hive) 55 { 56 ULONG i; 57 PHBIN Bin; 58 ULONG Storage; 59 60 for (Storage = 0; Storage < Hive->StorageTypeCount; Storage++) 61 { 62 Bin = NULL; 63 for (i = 0; i < Hive->Storage[Storage].Length; i++) 64 { 65 if (Hive->Storage[Storage].BlockList[i].BinAddress == (ULONG_PTR)NULL) 66 continue; 67 if (Hive->Storage[Storage].BlockList[i].BinAddress != (ULONG_PTR)Bin) 68 { 69 Bin = (PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress; 70 Hive->Free((PHBIN)Hive->Storage[Storage].BlockList[i].BinAddress, 0); 71 } 72 Hive->Storage[Storage].BlockList[i].BinAddress = (ULONG_PTR)NULL; 73 Hive->Storage[Storage].BlockList[i].BlockAddress = (ULONG_PTR)NULL; 74 } 75 76 if (Hive->Storage[Storage].Length) 77 Hive->Free(Hive->Storage[Storage].BlockList, 0); 78 } 79 } 80 81 /** 82 * @name HvpAllocBaseBlockAligned 83 * 84 * Internal helper function to allocate cluster-aligned hive base blocks. 85 */ 86 static __inline PHBASE_BLOCK 87 HvpAllocBaseBlockAligned( 88 IN PHHIVE Hive, 89 IN BOOLEAN Paged, 90 IN ULONG Tag) 91 { 92 PHBASE_BLOCK BaseBlock; 93 ULONG Alignment; 94 95 ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); 96 97 /* Allocate the buffer */ 98 BaseBlock = Hive->Allocate(Hive->BaseBlockAlloc, Paged, Tag); 99 if (!BaseBlock) return NULL; 100 101 /* Check for, and enforce, alignment */ 102 Alignment = Hive->Cluster * HSECTOR_SIZE -1; 103 if ((ULONG_PTR)BaseBlock & Alignment) 104 { 105 /* Free the old header and reallocate a new one, always paged */ 106 Hive->Free(BaseBlock, Hive->BaseBlockAlloc); 107 BaseBlock = Hive->Allocate(PAGE_SIZE, TRUE, Tag); 108 if (!BaseBlock) return NULL; 109 110 Hive->BaseBlockAlloc = PAGE_SIZE; 111 } 112 113 return BaseBlock; 114 } 115 116 /** 117 * @name HvpInitFileName 118 * 119 * Internal function to initialize the UNICODE NULL-terminated hive file name 120 * member of a hive header by copying the last 31 characters of the file name. 121 * Mainly used for debugging purposes. 122 */ 123 static VOID 124 HvpInitFileName( 125 IN OUT PHBASE_BLOCK BaseBlock, 126 IN PCUNICODE_STRING FileName OPTIONAL) 127 { 128 ULONG_PTR Offset; 129 SIZE_T Length; 130 131 /* Always NULL-initialize */ 132 RtlZeroMemory(BaseBlock->FileName, (HIVE_FILENAME_MAXLEN + 1) * sizeof(WCHAR)); 133 134 /* Copy the 31 last characters of the hive file name if any */ 135 if (!FileName) return; 136 137 if (FileName->Length / sizeof(WCHAR) <= HIVE_FILENAME_MAXLEN) 138 { 139 Offset = 0; 140 Length = FileName->Length; 141 } 142 else 143 { 144 Offset = FileName->Length / sizeof(WCHAR) - HIVE_FILENAME_MAXLEN; 145 Length = HIVE_FILENAME_MAXLEN * sizeof(WCHAR); 146 } 147 148 RtlCopyMemory(BaseBlock->FileName, FileName->Buffer + Offset, Length); 149 } 150 151 /** 152 * @name HvpCreateHive 153 * 154 * Internal helper function to initialize a hive descriptor structure 155 * for a newly created hive in memory. 156 * 157 * @see HvInitialize 158 */ 159 NTSTATUS CMAPI 160 HvpCreateHive( 161 IN OUT PHHIVE RegistryHive, 162 IN PCUNICODE_STRING FileName OPTIONAL) 163 { 164 PHBASE_BLOCK BaseBlock; 165 ULONG Index; 166 167 /* Allocate the base block */ 168 BaseBlock = HvpAllocBaseBlockAligned(RegistryHive, FALSE, TAG_CM); 169 if (BaseBlock == NULL) 170 return STATUS_NO_MEMORY; 171 172 /* Clear it */ 173 RtlZeroMemory(BaseBlock, RegistryHive->BaseBlockAlloc); 174 175 BaseBlock->Signature = HV_HBLOCK_SIGNATURE; 176 BaseBlock->Major = HSYS_MAJOR; 177 BaseBlock->Minor = HSYS_MINOR; 178 BaseBlock->Type = HFILE_TYPE_PRIMARY; 179 BaseBlock->Format = HBASE_FORMAT_MEMORY; 180 BaseBlock->Cluster = 1; 181 BaseBlock->RootCell = HCELL_NIL; 182 BaseBlock->Length = 0; 183 BaseBlock->Sequence1 = 1; 184 BaseBlock->Sequence2 = 1; 185 BaseBlock->TimeStamp.QuadPart = 0ULL; 186 187 /* 188 * No need to compute the checksum since 189 * the hive resides only in memory so far. 190 */ 191 BaseBlock->CheckSum = 0; 192 193 /* Set default boot type */ 194 BaseBlock->BootType = 0; 195 196 /* Setup hive data */ 197 RegistryHive->BaseBlock = BaseBlock; 198 RegistryHive->Version = BaseBlock->Minor; // == HSYS_MINOR 199 200 for (Index = 0; Index < 24; Index++) 201 { 202 RegistryHive->Storage[Stable].FreeDisplay[Index] = HCELL_NIL; 203 RegistryHive->Storage[Volatile].FreeDisplay[Index] = HCELL_NIL; 204 } 205 206 HvpInitFileName(BaseBlock, FileName); 207 208 return STATUS_SUCCESS; 209 } 210 211 /** 212 * @name HvpInitializeMemoryHive 213 * 214 * Internal helper function to initialize hive descriptor structure for 215 * an existing hive stored in memory. The data of the hive is copied 216 * and it is prepared for read/write access. 217 * 218 * @see HvInitialize 219 */ 220 NTSTATUS CMAPI 221 HvpInitializeMemoryHive( 222 PHHIVE Hive, 223 PHBASE_BLOCK ChunkBase, 224 IN PCUNICODE_STRING FileName OPTIONAL) 225 { 226 SIZE_T BlockIndex; 227 PHBIN Bin, NewBin; 228 ULONG i; 229 ULONG BitmapSize; 230 PULONG BitmapBuffer; 231 SIZE_T ChunkSize; 232 233 ChunkSize = ChunkBase->Length; 234 DPRINT("ChunkSize: %zx\n", ChunkSize); 235 236 if (ChunkSize < sizeof(HBASE_BLOCK) || 237 !HvpVerifyHiveHeader(ChunkBase)) 238 { 239 DPRINT1("Registry is corrupt: ChunkSize 0x%zx < sizeof(HBASE_BLOCK) 0x%zx, " 240 "or HvpVerifyHiveHeader() failed\n", ChunkSize, sizeof(HBASE_BLOCK)); 241 return STATUS_REGISTRY_CORRUPT; 242 } 243 244 /* Allocate the base block */ 245 Hive->BaseBlock = HvpAllocBaseBlockAligned(Hive, FALSE, TAG_CM); 246 if (Hive->BaseBlock == NULL) 247 return STATUS_NO_MEMORY; 248 249 RtlCopyMemory(Hive->BaseBlock, ChunkBase, sizeof(HBASE_BLOCK)); 250 251 /* Setup hive data */ 252 Hive->Version = ChunkBase->Minor; 253 254 /* 255 * Build a block list from the in-memory chunk and copy the data as 256 * we go. 257 */ 258 259 Hive->Storage[Stable].Length = (ULONG)(ChunkSize / HBLOCK_SIZE); 260 Hive->Storage[Stable].BlockList = 261 Hive->Allocate(Hive->Storage[Stable].Length * 262 sizeof(HMAP_ENTRY), FALSE, TAG_CM); 263 if (Hive->Storage[Stable].BlockList == NULL) 264 { 265 DPRINT1("Allocating block list failed\n"); 266 Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); 267 return STATUS_NO_MEMORY; 268 } 269 270 for (BlockIndex = 0; BlockIndex < Hive->Storage[Stable].Length; ) 271 { 272 Bin = (PHBIN)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HBLOCK_SIZE); 273 if (Bin->Signature != HV_HBIN_SIGNATURE || 274 (Bin->Size % HBLOCK_SIZE) != 0) 275 { 276 DPRINT1("Invalid bin at BlockIndex %lu, Signature 0x%x, Size 0x%x\n", 277 (unsigned long)BlockIndex, (unsigned)Bin->Signature, (unsigned)Bin->Size); 278 Hive->Free(Hive->Storage[Stable].BlockList, 0); 279 Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); 280 return STATUS_REGISTRY_CORRUPT; 281 } 282 283 NewBin = Hive->Allocate(Bin->Size, TRUE, TAG_CM); 284 if (NewBin == NULL) 285 { 286 Hive->Free(Hive->Storage[Stable].BlockList, 0); 287 Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); 288 return STATUS_NO_MEMORY; 289 } 290 291 Hive->Storage[Stable].BlockList[BlockIndex].BinAddress = (ULONG_PTR)NewBin; 292 Hive->Storage[Stable].BlockList[BlockIndex].BlockAddress = (ULONG_PTR)NewBin; 293 294 RtlCopyMemory(NewBin, Bin, Bin->Size); 295 296 if (Bin->Size > HBLOCK_SIZE) 297 { 298 for (i = 1; i < Bin->Size / HBLOCK_SIZE; i++) 299 { 300 Hive->Storage[Stable].BlockList[BlockIndex + i].BinAddress = (ULONG_PTR)NewBin; 301 Hive->Storage[Stable].BlockList[BlockIndex + i].BlockAddress = 302 ((ULONG_PTR)NewBin + (i * HBLOCK_SIZE)); 303 } 304 } 305 306 BlockIndex += Bin->Size / HBLOCK_SIZE; 307 } 308 309 if (!NT_SUCCESS(HvpCreateHiveFreeCellList(Hive))) 310 { 311 HvpFreeHiveBins(Hive); 312 Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); 313 return STATUS_NO_MEMORY; 314 } 315 316 BitmapSize = ROUND_UP(Hive->Storage[Stable].Length, 317 sizeof(ULONG) * 8) / 8; 318 BitmapBuffer = (PULONG)Hive->Allocate(BitmapSize, TRUE, TAG_CM); 319 if (BitmapBuffer == NULL) 320 { 321 HvpFreeHiveBins(Hive); 322 Hive->Free(Hive->BaseBlock, Hive->BaseBlockAlloc); 323 return STATUS_NO_MEMORY; 324 } 325 326 RtlInitializeBitMap(&Hive->DirtyVector, BitmapBuffer, BitmapSize * 8); 327 RtlClearAllBits(&Hive->DirtyVector); 328 329 HvpInitFileName(Hive->BaseBlock, FileName); 330 331 return STATUS_SUCCESS; 332 } 333 334 /** 335 * @name HvpInitializeFlatHive 336 * 337 * Internal helper function to initialize hive descriptor structure for 338 * a hive stored in memory. The in-memory data of the hive are directly 339 * used and it is read-only accessible. 340 * 341 * @see HvInitialize 342 */ 343 NTSTATUS CMAPI 344 HvpInitializeFlatHive( 345 PHHIVE Hive, 346 PHBASE_BLOCK ChunkBase) 347 { 348 if (!HvpVerifyHiveHeader(ChunkBase)) 349 return STATUS_REGISTRY_CORRUPT; 350 351 /* Setup hive data */ 352 Hive->BaseBlock = ChunkBase; 353 Hive->Version = ChunkBase->Minor; 354 Hive->Flat = TRUE; 355 Hive->ReadOnly = TRUE; 356 357 Hive->StorageTypeCount = 1; 358 359 /* Set default boot type */ 360 ChunkBase->BootType = 0; 361 362 return STATUS_SUCCESS; 363 } 364 365 typedef enum _RESULT 366 { 367 NotHive, 368 Fail, 369 NoMemory, 370 HiveSuccess, 371 RecoverHeader, 372 RecoverData, 373 SelfHeal 374 } RESULT; 375 376 RESULT CMAPI 377 HvpGetHiveHeader(IN PHHIVE Hive, 378 IN PHBASE_BLOCK *HiveBaseBlock, 379 IN PLARGE_INTEGER TimeStamp) 380 { 381 PHBASE_BLOCK BaseBlock; 382 ULONG Result; 383 ULONG Offset = 0; 384 385 ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); 386 387 /* Assume failure and allocate the base block */ 388 *HiveBaseBlock = NULL; 389 BaseBlock = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM); 390 if (!BaseBlock) return NoMemory; 391 392 /* Clear it */ 393 RtlZeroMemory(BaseBlock, sizeof(HBASE_BLOCK)); 394 395 /* Now read it from disk */ 396 Result = Hive->FileRead(Hive, 397 HFILE_TYPE_PRIMARY, 398 &Offset, 399 BaseBlock, 400 Hive->Cluster * HSECTOR_SIZE); 401 402 /* Couldn't read: assume it's not a hive */ 403 if (!Result) return NotHive; 404 405 /* Do validation */ 406 if (!HvpVerifyHiveHeader(BaseBlock)) return NotHive; 407 408 /* Return information */ 409 *HiveBaseBlock = BaseBlock; 410 *TimeStamp = BaseBlock->TimeStamp; 411 return HiveSuccess; 412 } 413 414 NTSTATUS CMAPI 415 HvLoadHive(IN PHHIVE Hive, 416 IN PCUNICODE_STRING FileName OPTIONAL) 417 { 418 NTSTATUS Status; 419 PHBASE_BLOCK BaseBlock = NULL; 420 ULONG Result; 421 LARGE_INTEGER TimeStamp; 422 ULONG Offset = 0; 423 PVOID HiveData; 424 ULONG FileSize; 425 426 /* Get the hive header */ 427 Result = HvpGetHiveHeader(Hive, &BaseBlock, &TimeStamp); 428 switch (Result) 429 { 430 /* Out of memory */ 431 case NoMemory: 432 433 /* Fail */ 434 return STATUS_INSUFFICIENT_RESOURCES; 435 436 /* Not a hive */ 437 case NotHive: 438 439 /* Fail */ 440 return STATUS_NOT_REGISTRY_FILE; 441 442 /* Has recovery data */ 443 case RecoverData: 444 case RecoverHeader: 445 446 /* Fail */ 447 return STATUS_REGISTRY_CORRUPT; 448 } 449 450 /* Set default boot type */ 451 BaseBlock->BootType = 0; 452 453 /* Setup hive data */ 454 Hive->BaseBlock = BaseBlock; 455 Hive->Version = BaseBlock->Minor; 456 457 /* Allocate a buffer large enough to hold the hive */ 458 FileSize = HBLOCK_SIZE + BaseBlock->Length; // == sizeof(HBASE_BLOCK) + BaseBlock->Length; 459 HiveData = Hive->Allocate(FileSize, TRUE, TAG_CM); 460 if (!HiveData) 461 { 462 Hive->Free(BaseBlock, Hive->BaseBlockAlloc); 463 return STATUS_INSUFFICIENT_RESOURCES; 464 } 465 466 /* Now read the whole hive */ 467 Result = Hive->FileRead(Hive, 468 HFILE_TYPE_PRIMARY, 469 &Offset, 470 HiveData, 471 FileSize); 472 if (!Result) 473 { 474 Hive->Free(HiveData, FileSize); 475 Hive->Free(BaseBlock, Hive->BaseBlockAlloc); 476 return STATUS_NOT_REGISTRY_FILE; 477 } 478 479 // This is a HACK! 480 /* Free our base block... it's usless in this implementation */ 481 Hive->Free(BaseBlock, Hive->BaseBlockAlloc); 482 483 /* Initialize the hive directly from memory */ 484 Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); 485 if (!NT_SUCCESS(Status)) 486 Hive->Free(HiveData, FileSize); 487 488 return Status; 489 } 490 491 /** 492 * @name HvInitialize 493 * 494 * Allocate a new hive descriptor structure and intialize it. 495 * 496 * @param RegistryHive 497 * Output variable to store pointer to the hive descriptor. 498 * @param OperationType 499 * - HV_OPERATION_CREATE_HIVE 500 * Create a new hive for read/write access. 501 * - HV_OPERATION_MEMORY 502 * Load and copy in-memory hive for read/write access. The 503 * pointer to data passed to this routine can be freed after 504 * the function is executed. 505 * - HV_OPERATION_MEMORY_INPLACE 506 * Load an in-memory hive for read-only access. The pointer 507 * to data passed to this routine MUSTN'T be freed until 508 * HvFree is called. 509 * @param ChunkBase 510 * Pointer to hive data. 511 * @param ChunkSize 512 * Size of passed hive data. 513 * 514 * @return 515 * STATUS_NO_MEMORY - A memory allocation failed. 516 * STATUS_REGISTRY_CORRUPT - Registry corruption was detected. 517 * STATUS_SUCCESS 518 * 519 * @see HvFree 520 */ 521 NTSTATUS CMAPI 522 HvInitialize( 523 PHHIVE RegistryHive, 524 ULONG OperationType, 525 ULONG HiveFlags, 526 ULONG FileType, 527 PVOID HiveData OPTIONAL, 528 PALLOCATE_ROUTINE Allocate, 529 PFREE_ROUTINE Free, 530 PFILE_SET_SIZE_ROUTINE FileSetSize, 531 PFILE_WRITE_ROUTINE FileWrite, 532 PFILE_READ_ROUTINE FileRead, 533 PFILE_FLUSH_ROUTINE FileFlush, 534 ULONG Cluster OPTIONAL, 535 PCUNICODE_STRING FileName OPTIONAL) 536 { 537 NTSTATUS Status; 538 PHHIVE Hive = RegistryHive; 539 540 /* 541 * Create a new hive structure that will hold all the maintenance data. 542 */ 543 544 RtlZeroMemory(Hive, sizeof(HHIVE)); 545 Hive->Signature = HV_HHIVE_SIGNATURE; 546 547 Hive->Allocate = Allocate; 548 Hive->Free = Free; 549 Hive->FileSetSize = FileSetSize; 550 Hive->FileWrite = FileWrite; 551 Hive->FileRead = FileRead; 552 Hive->FileFlush = FileFlush; 553 554 Hive->RefreshCount = 0; 555 Hive->StorageTypeCount = HTYPE_COUNT; 556 Hive->Cluster = Cluster; 557 Hive->BaseBlockAlloc = sizeof(HBASE_BLOCK); // == HBLOCK_SIZE 558 559 Hive->Version = HSYS_MINOR; 560 #if (NTDDI_VERSION < NTDDI_VISTA) 561 Hive->Log = (FileType == HFILE_TYPE_LOG); 562 #endif 563 Hive->HiveFlags = HiveFlags & ~HIVE_NOLAZYFLUSH; 564 565 // TODO: The CellRoutines point to different callbacks 566 // depending on the OperationType. 567 Hive->GetCellRoutine = HvpGetCellData; 568 Hive->ReleaseCellRoutine = NULL; 569 570 switch (OperationType) 571 { 572 case HINIT_CREATE: 573 Status = HvpCreateHive(Hive, FileName); 574 break; 575 576 case HINIT_MEMORY: 577 Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); 578 break; 579 580 case HINIT_FLAT: 581 Status = HvpInitializeFlatHive(Hive, HiveData); 582 break; 583 584 case HINIT_FILE: 585 { 586 Status = HvLoadHive(Hive, FileName); 587 if ((Status != STATUS_SUCCESS) && 588 (Status != STATUS_REGISTRY_RECOVERED)) 589 { 590 /* Unrecoverable failure */ 591 return Status; 592 } 593 594 /* Check for previous damage */ 595 ASSERT(Status != STATUS_REGISTRY_RECOVERED); 596 break; 597 } 598 599 case HINIT_MEMORY_INPLACE: 600 // Status = HvpInitializeMemoryInplaceHive(Hive, HiveData); 601 // break; 602 603 case HINIT_MAPFILE: 604 605 default: 606 /* FIXME: A better return status value is needed */ 607 Status = STATUS_NOT_IMPLEMENTED; 608 ASSERT(FALSE); 609 } 610 611 if (!NT_SUCCESS(Status)) return Status; 612 613 /* HACK: ROS: Init root key cell and prepare the hive */ 614 // r31253 615 // if (OperationType == HINIT_CREATE) CmCreateRootNode(Hive, L""); 616 if (OperationType != HINIT_CREATE) CmPrepareHive(Hive); 617 618 return Status; 619 } 620 621 /** 622 * @name HvFree 623 * 624 * Free all stroage and handles associated with hive descriptor. 625 * But do not free the hive descriptor itself. 626 */ 627 VOID CMAPI 628 HvFree( 629 PHHIVE RegistryHive) 630 { 631 if (!RegistryHive->ReadOnly) 632 { 633 /* Release hive bitmap */ 634 if (RegistryHive->DirtyVector.Buffer) 635 { 636 RegistryHive->Free(RegistryHive->DirtyVector.Buffer, 0); 637 } 638 639 HvpFreeHiveBins(RegistryHive); 640 641 /* Free the BaseBlock */ 642 if (RegistryHive->BaseBlock) 643 { 644 RegistryHive->Free(RegistryHive->BaseBlock, RegistryHive->BaseBlockAlloc); 645 RegistryHive->BaseBlock = NULL; 646 } 647 } 648 } 649 650 /* EOF */ 651