1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log/Alternate Writing 5 * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl 6 * Copyright 2005 Filip Navara <navaraf@reactos.org> 7 * Copyright 2021 Max Korostil 8 * Copyright 2022 George Bișoc <george.bisoc@reactos.org> 9 */ 10 11 #include "cmlib.h" 12 #define NDEBUG 13 #include <debug.h> 14 15 /* DECLARATIONS *************************************************************/ 16 17 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 18 BOOLEAN 19 NTAPI 20 IoSetThreadHardErrorMode( 21 _In_ BOOLEAN HardErrorEnabled); 22 #endif 23 24 /* GLOBALS ******************************************************************/ 25 26 /* PRIVATE FUNCTIONS ********************************************************/ 27 28 /** 29 * @brief 30 * Validates the base block header of a primary 31 * hive for consistency. 32 * 33 * @param[in] RegistryHive 34 * A pointer to a hive descriptor to look 35 * for the header block. 36 */ 37 static 38 VOID 39 HvpValidateBaseHeader( 40 _In_ PHHIVE RegistryHive) 41 { 42 PHBASE_BLOCK BaseBlock; 43 44 /* 45 * Cache the base block and validate it. 46 * Especially... 47 * 48 * 1. It must must have a valid signature. 49 * 2. It must have a valid format. 50 * 3. It must be of an adequate major version, 51 * not anything else. 52 */ 53 BaseBlock = RegistryHive->BaseBlock; 54 ASSERT(BaseBlock->Signature == HV_HBLOCK_SIGNATURE); 55 ASSERT(BaseBlock->Format == HBASE_FORMAT_MEMORY); 56 ASSERT(BaseBlock->Major == HSYS_MAJOR); 57 } 58 59 /** 60 * @unimplemented 61 * @brief 62 * Writes dirty data in a transacted way to a hive 63 * log file during hive syncing operation. Log 64 * files are used by the kernel/bootloader to 65 * perform recovery operations against a 66 * damaged primary hive. 67 * 68 * @param[in] RegistryHive 69 * A pointer to a hive descriptor where the log 70 * belongs to and of which we write data into the 71 * said log. 72 * 73 * @return 74 * Returns TRUE if log transaction writing has succeeded, 75 * FALSE otherwise. 76 * 77 * @remarks 78 * The function is not completely implemented, that is, 79 * it lacks the implementation for growing the log file size. 80 * See the FIXME comment below for further details. 81 */ 82 static 83 BOOLEAN 84 CMAPI 85 HvpWriteLog( 86 _In_ PHHIVE RegistryHive) 87 { 88 BOOLEAN Success; 89 ULONG FileOffset; 90 ULONG BlockIndex; 91 ULONG LastIndex; 92 PVOID Block; 93 UINT32 BitmapSize, BufferSize; 94 PUCHAR HeaderBuffer, Ptr; 95 96 /* 97 * The hive log we are going to write data into 98 * has to be writable and with a sane storage. 99 */ 100 ASSERT(!RegistryHive->ReadOnly); 101 ASSERT(RegistryHive->BaseBlock->Length == 102 RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); 103 104 /* Validate the base header before we go further */ 105 HvpValidateBaseHeader(RegistryHive); 106 107 /* 108 * The sequences can diverge during a forced system shutdown 109 * occurrence, such as during a power failure, a hardware 110 * failure or during a system crash, and when one of the 111 * sequences have been modified during writing into the log 112 * or hive. In such cases the hive needs a repair. 113 */ 114 if (RegistryHive->BaseBlock->Sequence1 != 115 RegistryHive->BaseBlock->Sequence2) 116 { 117 DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n", 118 RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2); 119 return FALSE; 120 } 121 122 /* 123 * FIXME: We must set a new file size for this log 124 * here but ReactOS lacks the necessary code implementation 125 * that manages the growing and shrinking of a hive's log 126 * size. So for now don't set any new size for the log. 127 */ 128 129 /* 130 * Now calculate the bitmap and buffer sizes to hold up our 131 * contents in a buffer. 132 */ 133 BitmapSize = ROUND_UP(sizeof(ULONG) + RegistryHive->DirtyVector.SizeOfBitMap / 8, HSECTOR_SIZE); 134 BufferSize = HV_LOG_HEADER_SIZE + BitmapSize; 135 136 /* Now allocate the base header block buffer */ 137 HeaderBuffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM); 138 if (!HeaderBuffer) 139 { 140 DPRINT1("Couldn't allocate buffer for base header block\n"); 141 return FALSE; 142 } 143 144 /* Great, now zero out the buffer */ 145 RtlZeroMemory(HeaderBuffer, BufferSize); 146 147 /* 148 * Update the base block of this hive and 149 * increment the primary sequence number 150 * as we are at the half of the work. 151 */ 152 RegistryHive->BaseBlock->Type = HFILE_TYPE_LOG; 153 RegistryHive->BaseBlock->Sequence1++; 154 RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); 155 156 /* Copy the base block header */ 157 RtlCopyMemory(HeaderBuffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE); 158 Ptr = HeaderBuffer + HV_LOG_HEADER_SIZE; 159 160 /* Copy the dirty vector */ 161 *((PULONG)Ptr) = HV_LOG_DIRTY_SIGNATURE; 162 Ptr += sizeof(HV_LOG_DIRTY_SIGNATURE); 163 164 /* 165 * FIXME: In ReactOS a vector contains one bit per block 166 * whereas in Windows each bit within a vector is per 167 * sector. Furthermore, the dirty blocks within a respective 168 * hive has to be marked as such in an appropriate function 169 * for this purpose (probably HvMarkDirty or similar). 170 * 171 * For the moment being, mark the relevant dirty blocks 172 * here. 173 */ 174 BlockIndex = 0; 175 while (BlockIndex < RegistryHive->Storage[Stable].Length) 176 { 177 /* Check if the block is clean or we're past the last block */ 178 LastIndex = BlockIndex; 179 BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); 180 if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) 181 { 182 break; 183 } 184 185 /* 186 * Mark this block as dirty and go to the next one. 187 * 188 * FIXME: We should rather use RtlSetBits but that crashes 189 * the system with a bugckeck. So for now mark blocks manually 190 * by hand. 191 */ 192 Ptr[BlockIndex] = HV_LOG_DIRTY_BLOCK; 193 BlockIndex++; 194 } 195 196 /* Now write the hive header and block bitmap into the log */ 197 FileOffset = 0; 198 Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, 199 &FileOffset, HeaderBuffer, BufferSize); 200 RegistryHive->Free(HeaderBuffer, 0); 201 if (!Success) 202 { 203 DPRINT1("Failed to write the hive header block to log (primary sequence)\n"); 204 return FALSE; 205 } 206 207 /* Now write the actual dirty data to log */ 208 FileOffset = BufferSize; 209 BlockIndex = 0; 210 while (BlockIndex < RegistryHive->Storage[Stable].Length) 211 { 212 /* Check if the block is clean or we're past the last block */ 213 LastIndex = BlockIndex; 214 BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); 215 if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) 216 { 217 break; 218 } 219 220 /* Get the block */ 221 Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; 222 223 /* Write it to log */ 224 Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, 225 &FileOffset, Block, HBLOCK_SIZE); 226 if (!Success) 227 { 228 DPRINT1("Failed to write dirty block to log (block 0x%p, block index 0x%x)\n", Block, BlockIndex); 229 return FALSE; 230 } 231 232 /* Grow up the file offset as we go to the next block */ 233 BlockIndex++; 234 FileOffset += HBLOCK_SIZE; 235 } 236 237 /* 238 * We wrote the header and body of log with dirty, 239 * data do a flush immediately. 240 */ 241 Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0); 242 if (!Success) 243 { 244 DPRINT1("Failed to flush the log\n"); 245 return FALSE; 246 } 247 248 /* 249 * OK, we're now at 80% of the work done. 250 * Increment the secondary sequence and flush 251 * the log again. We can have a fully successful 252 * transacted write of a log if the sequences 253 * are synced up properly. 254 */ 255 RegistryHive->BaseBlock->Sequence2++; 256 RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); 257 258 /* Write new stuff into log first */ 259 FileOffset = 0; 260 Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, 261 &FileOffset, RegistryHive->BaseBlock, 262 HV_LOG_HEADER_SIZE); 263 if (!Success) 264 { 265 DPRINT1("Failed to write the log file (secondary sequence)\n"); 266 return FALSE; 267 } 268 269 /* Flush it finally */ 270 Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0); 271 if (!Success) 272 { 273 DPRINT1("Failed to flush the log\n"); 274 return FALSE; 275 } 276 277 return TRUE; 278 } 279 280 /** 281 * @brief 282 * Writes data (dirty or non) to a primary hive during 283 * syncing operation. Hive writing is also performed 284 * during a flush occurrence on request by the system. 285 * 286 * @param[in] RegistryHive 287 * A pointer to a hive descriptor where the data is 288 * to be written to that hive. 289 * 290 * @param[in] OnlyDirty 291 * If set to TRUE, the function only looks for dirty 292 * data to be written to the primary hive, otherwise if 293 * it's set to FALSE then the function writes all the data. 294 * 295 * @param[in] FileType 296 * The file type of a registry hive. This can be HFILE_TYPE_PRIMARY 297 * or HFILE_TYPE_ALTERNATE. 298 * 299 * @return 300 * Returns TRUE if writing to hive has succeeded, 301 * FALSE otherwise. 302 * 303 * @remarks 304 * The on-disk header metadata of a hive is already written with type 305 * of HFILE_TYPE_PRIMARY, regardless of what file type the caller submits, 306 * as an alternate hive is basically a mirror of the primary hive. 307 */ 308 static 309 BOOLEAN 310 CMAPI 311 HvpWriteHive( 312 _In_ PHHIVE RegistryHive, 313 _In_ BOOLEAN OnlyDirty, 314 _In_ ULONG FileType) 315 { 316 BOOLEAN Success; 317 ULONG FileOffset; 318 ULONG BlockIndex; 319 ULONG LastIndex; 320 PVOID Block; 321 322 ASSERT(!RegistryHive->ReadOnly); 323 ASSERT(RegistryHive->BaseBlock->Length == 324 RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); 325 ASSERT(RegistryHive->BaseBlock->RootCell != HCELL_NIL); 326 327 /* Validate the base header before we go further */ 328 HvpValidateBaseHeader(RegistryHive); 329 330 /* 331 * The sequences can diverge during a forced system shutdown 332 * occurrence, such as during a power failure, a hardware 333 * failure or during a system crash, and when one of the 334 * sequences have been modified during writing into the log 335 * or hive. In such cases the hive needs a repair. 336 */ 337 if (RegistryHive->BaseBlock->Sequence1 != 338 RegistryHive->BaseBlock->Sequence2) 339 { 340 DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n", 341 RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2); 342 return FALSE; 343 } 344 345 /* 346 * Update the primary sequence number and write 347 * the base block to hive. 348 */ 349 RegistryHive->BaseBlock->Type = HFILE_TYPE_PRIMARY; 350 RegistryHive->BaseBlock->Sequence1++; 351 RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); 352 353 /* Write hive block */ 354 FileOffset = 0; 355 Success = RegistryHive->FileWrite(RegistryHive, FileType, 356 &FileOffset, RegistryHive->BaseBlock, 357 sizeof(HBASE_BLOCK)); 358 if (!Success) 359 { 360 DPRINT1("Failed to write the base block header to primary hive (primary sequence)\n"); 361 return FALSE; 362 } 363 364 /* Write the whole primary hive, block by block */ 365 BlockIndex = 0; 366 while (BlockIndex < RegistryHive->Storage[Stable].Length) 367 { 368 /* 369 * If we have to synchronize the registry hive we 370 * want to look for dirty blocks to reflect the new 371 * updates done to the hive. Otherwise just write 372 * all the blocks as if we were doing a regular 373 * hive write. 374 */ 375 if (OnlyDirty) 376 { 377 /* Check if the block is clean or we're past the last block */ 378 LastIndex = BlockIndex; 379 BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); 380 if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) 381 { 382 break; 383 } 384 } 385 386 /* Get the block and offset position */ 387 Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; 388 FileOffset = (BlockIndex + 1) * HBLOCK_SIZE; 389 390 /* Now write this block to primary hive file */ 391 Success = RegistryHive->FileWrite(RegistryHive, FileType, 392 &FileOffset, Block, HBLOCK_SIZE); 393 if (!Success) 394 { 395 DPRINT1("Failed to write hive block to primary hive file (block 0x%p, block index 0x%x)\n", 396 Block, BlockIndex); 397 return FALSE; 398 } 399 400 /* Go to the next block */ 401 BlockIndex++; 402 } 403 404 /* 405 * We wrote all the hive contents to the file, we 406 * must flush the changes to disk now. 407 */ 408 Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); 409 if (!Success) 410 { 411 DPRINT1("Failed to flush the primary hive\n"); 412 return FALSE; 413 } 414 415 /* 416 * Increment the secondary sequence number and 417 * update the checksum. A successful hive write 418 * transaction is when both of sequences are the 419 * same, indicating the write operation didn't 420 * fail. 421 */ 422 RegistryHive->BaseBlock->Sequence2++; 423 RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); 424 425 /* Write hive block */ 426 FileOffset = 0; 427 Success = RegistryHive->FileWrite(RegistryHive, FileType, 428 &FileOffset, RegistryHive->BaseBlock, 429 sizeof(HBASE_BLOCK)); 430 if (!Success) 431 { 432 DPRINT1("Failed to write the base block header to primary hive (secondary sequence)\n"); 433 return FALSE; 434 } 435 436 /* Flush the hive immediately */ 437 Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); 438 if (!Success) 439 { 440 DPRINT1("Failed to flush the primary hive\n"); 441 return FALSE; 442 } 443 444 return TRUE; 445 } 446 447 /* PUBLIC FUNCTIONS ***********************************************************/ 448 449 /** 450 * @brief 451 * Synchronizes a registry hive with latest updates 452 * from dirty data present in volatile memory, aka RAM. 453 * It writes both to hive log and corresponding primary 454 * hive. Syncing is done on request by the system during 455 * a flush occurrence. 456 * 457 * @param[in] RegistryHive 458 * A pointer to a hive descriptor where syncing is 459 * to be performed. 460 * 461 * @return 462 * Returns TRUE if syncing has succeeded, FALSE otherwise. 463 */ 464 BOOLEAN 465 CMAPI 466 HvSyncHive( 467 _In_ PHHIVE RegistryHive) 468 { 469 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 470 BOOLEAN HardErrors; 471 #endif 472 473 ASSERT(!RegistryHive->ReadOnly); 474 ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 475 476 /* Avoid any write operations on volatile hives */ 477 if (RegistryHive->HiveFlags & HIVE_VOLATILE) 478 { 479 DPRINT("Hive 0x%p is volatile\n", RegistryHive); 480 return TRUE; 481 } 482 483 /* 484 * Check if there's any dirty data in the vector. 485 * A space with clean blocks would be pointless for 486 * a log because we want to write dirty data in and 487 * sync up, not clean data. So just consider our 488 * job as done as there's literally nothing to do. 489 */ 490 if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~HV_CLEAN_BLOCK) 491 { 492 DPRINT("The dirty vector has clean data, nothing to do\n"); 493 return TRUE; 494 } 495 496 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 497 /* Disable hard errors before syncing the hive */ 498 HardErrors = IoSetThreadHardErrorMode(FALSE); 499 #endif 500 501 #if !defined(_BLDR_) 502 /* Update hive header modification time */ 503 KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); 504 #endif 505 506 /* Update the hive log file if present */ 507 if (RegistryHive->Log) 508 { 509 if (!HvpWriteLog(RegistryHive)) 510 { 511 DPRINT1("Failed to write a log whilst syncing the hive\n"); 512 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 513 IoSetThreadHardErrorMode(HardErrors); 514 #endif 515 return FALSE; 516 } 517 } 518 519 /* Update the primary hive file */ 520 if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY)) 521 { 522 DPRINT1("Failed to write the primary hive\n"); 523 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 524 IoSetThreadHardErrorMode(HardErrors); 525 #endif 526 return FALSE; 527 } 528 529 /* Update the alternate hive file if present */ 530 if (RegistryHive->Alternate) 531 { 532 if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_ALTERNATE)) 533 { 534 DPRINT1("Failed to write the alternate hive\n"); 535 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 536 IoSetThreadHardErrorMode(HardErrors); 537 #endif 538 return FALSE; 539 } 540 } 541 542 /* Clear dirty bitmap. */ 543 RtlClearAllBits(&RegistryHive->DirtyVector); 544 RegistryHive->DirtyCount = 0; 545 546 #if !defined(CMLIB_HOST) && !defined(_BLDR_) 547 IoSetThreadHardErrorMode(HardErrors); 548 #endif 549 return TRUE; 550 } 551 552 /** 553 * @unimplemented 554 * @brief 555 * Determines whether a registry hive needs 556 * to be shrinked or not based on its overall 557 * size of the hive space to avoid unnecessary 558 * bloat. 559 * 560 * @param[in] RegistryHive 561 * A pointer to a hive descriptor where hive 562 * shrinking is to be determined. 563 * 564 * @return 565 * Returns TRUE if hive shrinking needs to be 566 * done, FALSE otherwise. 567 */ 568 BOOLEAN 569 CMAPI 570 HvHiveWillShrink( 571 _In_ PHHIVE RegistryHive) 572 { 573 /* No shrinking yet */ 574 UNIMPLEMENTED_ONCE; 575 return FALSE; 576 } 577 578 /** 579 * @brief 580 * Writes data to a registry hive. Unlike 581 * HvSyncHive, this function just writes 582 * the wholy registry data to a primary hive, 583 * ignoring if a certain data block is dirty 584 * or not. 585 * 586 * @param[in] RegistryHive 587 * A pointer to a hive descriptor where data 588 * is be written into. 589 * 590 * @return 591 * Returns TRUE if hive writing has succeeded, 592 * FALSE otherwise. 593 */ 594 BOOLEAN 595 CMAPI 596 HvWriteHive( 597 _In_ PHHIVE RegistryHive) 598 { 599 ASSERT(!RegistryHive->ReadOnly); 600 ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 601 602 #if !defined(_BLDR_) 603 /* Update hive header modification time */ 604 KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); 605 #endif 606 607 /* Update hive file */ 608 if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_PRIMARY)) 609 { 610 DPRINT1("Failed to write the hive\n"); 611 return FALSE; 612 } 613 614 return TRUE; 615 } 616 617 /** 618 * @brief 619 * Writes data to an alternate registry hive. 620 * An alternate hive is usually backed up by a primary 621 * hive. This function is tipically used to force write 622 * data into the alternate hive if both hives no longer match. 623 * 624 * @param[in] RegistryHive 625 * A pointer to a hive descriptor where data 626 * is to be written into. 627 * 628 * @return 629 * Returns TRUE if hive writing has succeeded, 630 * FALSE otherwise. 631 */ 632 BOOLEAN 633 CMAPI 634 HvWriteAlternateHive( 635 _In_ PHHIVE RegistryHive) 636 { 637 ASSERT(!RegistryHive->ReadOnly); 638 ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 639 ASSERT(RegistryHive->Alternate); 640 641 #if !defined(_BLDR_) 642 /* Update hive header modification time */ 643 KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); 644 #endif 645 646 /* Update hive file */ 647 if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_ALTERNATE)) 648 { 649 DPRINT1("Failed to write the alternate hive\n"); 650 return FALSE; 651 } 652 653 return TRUE; 654 } 655 656 /** 657 * @brief 658 * Synchronizes a hive with recovered 659 * data during a healing/resuscitation 660 * operation of the registry. 661 * 662 * @param[in] RegistryHive 663 * A pointer to a hive descriptor where data 664 * syncing is to be done. 665 * 666 * @return 667 * Returns TRUE if hive syncing during recovery 668 * succeeded, FALSE otherwise. 669 */ 670 BOOLEAN 671 CMAPI 672 HvSyncHiveFromRecover( 673 _In_ PHHIVE RegistryHive) 674 { 675 ASSERT(!RegistryHive->ReadOnly); 676 ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); 677 678 /* Call the private API call to do the deed for us */ 679 return HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY); 680 } 681 682 /* EOF */ 683