/* * PROJECT: ReactOS Kernel * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Configuration Manager Library - Registry Syncing & Hive/Log/Alternate Writing * COPYRIGHT: Copyright 2001 - 2005 Eric Kohl * Copyright 2005 Filip Navara * Copyright 2021 Max Korostil * Copyright 2022 George Bișoc */ #include "cmlib.h" #define NDEBUG #include /* DECLARATIONS *************************************************************/ #if !defined(CMLIB_HOST) && !defined(_BLDR_) BOOLEAN NTAPI IoSetThreadHardErrorMode( _In_ BOOLEAN HardErrorEnabled); #endif /* GLOBALS ******************************************************************/ /* PRIVATE FUNCTIONS ********************************************************/ /** * @brief * Validates the base block header of a primary * hive for consistency. * * @param[in] RegistryHive * A pointer to a hive descriptor to look * for the header block. */ static VOID HvpValidateBaseHeader( _In_ PHHIVE RegistryHive) { PHBASE_BLOCK BaseBlock; /* * Cache the base block and validate it. * Especially... * * 1. It must must have a valid signature. * 2. It must have a valid format. * 3. It must be of an adequate major version, * not anything else. */ BaseBlock = RegistryHive->BaseBlock; ASSERT(BaseBlock->Signature == HV_HBLOCK_SIGNATURE); ASSERT(BaseBlock->Format == HBASE_FORMAT_MEMORY); ASSERT(BaseBlock->Major == HSYS_MAJOR); } /** * @unimplemented * @brief * Writes dirty data in a transacted way to a hive * log file during hive syncing operation. Log * files are used by the kernel/bootloader to * perform recovery operations against a * damaged primary hive. * * @param[in] RegistryHive * A pointer to a hive descriptor where the log * belongs to and of which we write data into the * said log. * * @return * Returns TRUE if log transaction writing has succeeded, * FALSE otherwise. * * @remarks * The function is not completely implemented, that is, * it lacks the implementation for growing the log file size. * See the FIXME comment below for further details. */ static BOOLEAN CMAPI HvpWriteLog( _In_ PHHIVE RegistryHive) { BOOLEAN Success; ULONG FileOffset; ULONG BlockIndex; ULONG LastIndex; PVOID Block; UINT32 BitmapSize, BufferSize; PUCHAR HeaderBuffer, Ptr; /* * The hive log we are going to write data into * has to be writable and with a sane storage. */ ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->BaseBlock->Length == RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); /* Validate the base header before we go further */ HvpValidateBaseHeader(RegistryHive); /* * The sequences can diverge during a forced system shutdown * occurrence, such as during a power failure, a hardware * failure or during a system crash, and when one of the * sequences have been modified during writing into the log * or hive. In such cases the hive needs a repair. */ if (RegistryHive->BaseBlock->Sequence1 != RegistryHive->BaseBlock->Sequence2) { DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n", RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2); return FALSE; } /* * FIXME: We must set a new file size for this log * here but ReactOS lacks the necessary code implementation * that manages the growing and shrinking of a hive's log * size. So for now don't set any new size for the log. */ /* * Now calculate the bitmap and buffer sizes to hold up our * contents in a buffer. */ BitmapSize = ROUND_UP(sizeof(ULONG) + RegistryHive->DirtyVector.SizeOfBitMap / 8, HSECTOR_SIZE); BufferSize = HV_LOG_HEADER_SIZE + BitmapSize; /* Now allocate the base header block buffer */ HeaderBuffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM); if (!HeaderBuffer) { DPRINT1("Couldn't allocate buffer for base header block\n"); return FALSE; } /* Great, now zero out the buffer */ RtlZeroMemory(HeaderBuffer, BufferSize); /* * Update the base block of this hive and * increment the primary sequence number * as we are at the half of the work. */ RegistryHive->BaseBlock->Type = HFILE_TYPE_LOG; RegistryHive->BaseBlock->Sequence1++; RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Copy the base block header */ RtlCopyMemory(HeaderBuffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE); Ptr = HeaderBuffer + HV_LOG_HEADER_SIZE; /* Copy the dirty vector */ *((PULONG)Ptr) = HV_LOG_DIRTY_SIGNATURE; Ptr += sizeof(HV_LOG_DIRTY_SIGNATURE); /* * FIXME: In ReactOS a vector contains one bit per block * whereas in Windows each bit within a vector is per * sector. Furthermore, the dirty blocks within a respective * hive has to be marked as such in an appropriate function * for this purpose (probably HvMarkDirty or similar). * * For the moment being, mark the relevant dirty blocks * here. */ BlockIndex = 0; while (BlockIndex < RegistryHive->Storage[Stable].Length) { /* Check if the block is clean or we're past the last block */ LastIndex = BlockIndex; BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) { break; } /* * Mark this block as dirty and go to the next one. * * FIXME: We should rather use RtlSetBits but that crashes * the system with a bugckeck. So for now mark blocks manually * by hand. */ Ptr[BlockIndex] = HV_LOG_DIRTY_BLOCK; BlockIndex++; } /* Now write the hive header and block bitmap into the log */ FileOffset = 0; Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, &FileOffset, HeaderBuffer, BufferSize); RegistryHive->Free(HeaderBuffer, 0); if (!Success) { DPRINT1("Failed to write the hive header block to log (primary sequence)\n"); return FALSE; } /* Now write the actual dirty data to log */ FileOffset = BufferSize; BlockIndex = 0; while (BlockIndex < RegistryHive->Storage[Stable].Length) { /* Check if the block is clean or we're past the last block */ LastIndex = BlockIndex; BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) { break; } /* Get the block */ Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; /* Write it to log */ Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, &FileOffset, Block, HBLOCK_SIZE); if (!Success) { DPRINT1("Failed to write dirty block to log (block 0x%p, block index 0x%x)\n", Block, BlockIndex); return FALSE; } /* Grow up the file offset as we go to the next block */ BlockIndex++; FileOffset += HBLOCK_SIZE; } /* * We wrote the header and body of log with dirty, * data do a flush immediately. */ Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0); if (!Success) { DPRINT1("Failed to flush the log\n"); return FALSE; } /* * OK, we're now at 80% of the work done. * Increment the secondary sequence and flush * the log again. We can have a fully successful * transacted write of a log if the sequences * are synced up properly. */ RegistryHive->BaseBlock->Sequence2++; RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Write new stuff into log first */ FileOffset = 0; Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, &FileOffset, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE); if (!Success) { DPRINT1("Failed to write the log file (secondary sequence)\n"); return FALSE; } /* Flush it finally */ Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0); if (!Success) { DPRINT1("Failed to flush the log\n"); return FALSE; } return TRUE; } /** * @brief * Writes data (dirty or non) to a primary hive during * syncing operation. Hive writing is also performed * during a flush occurrence on request by the system. * * @param[in] RegistryHive * A pointer to a hive descriptor where the data is * to be written to that hive. * * @param[in] OnlyDirty * If set to TRUE, the function only looks for dirty * data to be written to the primary hive, otherwise if * it's set to FALSE then the function writes all the data. * * @param[in] FileType * The file type of a registry hive. This can be HFILE_TYPE_PRIMARY * or HFILE_TYPE_ALTERNATE. * * @return * Returns TRUE if writing to hive has succeeded, * FALSE otherwise. * * @remarks * The on-disk header metadata of a hive is already written with type * of HFILE_TYPE_PRIMARY, regardless of what file type the caller submits, * as an alternate hive is basically a mirror of the primary hive. */ static BOOLEAN CMAPI HvpWriteHive( _In_ PHHIVE RegistryHive, _In_ BOOLEAN OnlyDirty, _In_ ULONG FileType) { BOOLEAN Success; ULONG FileOffset; ULONG BlockIndex; ULONG LastIndex; PVOID Block; ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->BaseBlock->Length == RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); ASSERT(RegistryHive->BaseBlock->RootCell != HCELL_NIL); /* Validate the base header before we go further */ HvpValidateBaseHeader(RegistryHive); /* * The sequences can diverge during a forced system shutdown * occurrence, such as during a power failure, a hardware * failure or during a system crash, and when one of the * sequences have been modified during writing into the log * or hive. In such cases the hive needs a repair. */ if (RegistryHive->BaseBlock->Sequence1 != RegistryHive->BaseBlock->Sequence2) { DPRINT1("The sequences DO NOT MATCH (Sequence1 == 0x%x, Sequence2 == 0x%x)\n", RegistryHive->BaseBlock->Sequence1, RegistryHive->BaseBlock->Sequence2); return FALSE; } /* * Update the primary sequence number and write * the base block to hive. */ RegistryHive->BaseBlock->Type = HFILE_TYPE_PRIMARY; RegistryHive->BaseBlock->Sequence1++; RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Write hive block */ FileOffset = 0; Success = RegistryHive->FileWrite(RegistryHive, FileType, &FileOffset, RegistryHive->BaseBlock, sizeof(HBASE_BLOCK)); if (!Success) { DPRINT1("Failed to write the base block header to primary hive (primary sequence)\n"); return FALSE; } /* Write the whole primary hive, block by block */ BlockIndex = 0; while (BlockIndex < RegistryHive->Storage[Stable].Length) { /* * If we have to synchronize the registry hive we * want to look for dirty blocks to reflect the new * updates done to the hive. Otherwise just write * all the blocks as if we were doing a regular * hive write. */ if (OnlyDirty) { /* Check if the block is clean or we're past the last block */ LastIndex = BlockIndex; BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex); if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) { break; } } /* Get the block and offset position */ Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; FileOffset = (BlockIndex + 1) * HBLOCK_SIZE; /* Now write this block to primary hive file */ Success = RegistryHive->FileWrite(RegistryHive, FileType, &FileOffset, Block, HBLOCK_SIZE); if (!Success) { DPRINT1("Failed to write hive block to primary hive file (block 0x%p, block index 0x%x)\n", Block, BlockIndex); return FALSE; } /* Go to the next block */ BlockIndex++; } /* * We wrote all the hive contents to the file, we * must flush the changes to disk now. */ Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); if (!Success) { DPRINT1("Failed to flush the primary hive\n"); return FALSE; } /* * Increment the secondary sequence number and * update the checksum. A successful hive write * transaction is when both of sequences are the * same, indicating the write operation didn't * fail. */ RegistryHive->BaseBlock->Sequence2++; RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Write hive block */ FileOffset = 0; Success = RegistryHive->FileWrite(RegistryHive, FileType, &FileOffset, RegistryHive->BaseBlock, sizeof(HBASE_BLOCK)); if (!Success) { DPRINT1("Failed to write the base block header to primary hive (secondary sequence)\n"); return FALSE; } /* Flush the hive immediately */ Success = RegistryHive->FileFlush(RegistryHive, FileType, NULL, 0); if (!Success) { DPRINT1("Failed to flush the primary hive\n"); return FALSE; } return TRUE; } /* PUBLIC FUNCTIONS ***********************************************************/ /** * @brief * Synchronizes a registry hive with latest updates * from dirty data present in volatile memory, aka RAM. * It writes both to hive log and corresponding primary * hive. Syncing is done on request by the system during * a flush occurrence. * * @param[in] RegistryHive * A pointer to a hive descriptor where syncing is * to be performed. * * @return * Returns TRUE if syncing has succeeded, FALSE otherwise. */ BOOLEAN CMAPI HvSyncHive( _In_ PHHIVE RegistryHive) { #if !defined(CMLIB_HOST) && !defined(_BLDR_) BOOLEAN HardErrors; #endif ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); /* Avoid any write operations on volatile hives */ if (RegistryHive->HiveFlags & HIVE_VOLATILE) { DPRINT("Hive 0x%p is volatile\n", RegistryHive); return TRUE; } /* * Check if there's any dirty data in the vector. * A space with clean blocks would be pointless for * a log because we want to write dirty data in and * sync up, not clean data. So just consider our * job as done as there's literally nothing to do. */ if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~HV_CLEAN_BLOCK) { DPRINT("The dirty vector has clean data, nothing to do\n"); return TRUE; } #if !defined(CMLIB_HOST) && !defined(_BLDR_) /* Disable hard errors before syncing the hive */ HardErrors = IoSetThreadHardErrorMode(FALSE); #endif #if !defined(_BLDR_) /* Update hive header modification time */ KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); #endif /* Update the hive log file if present */ if (RegistryHive->Log) { if (!HvpWriteLog(RegistryHive)) { DPRINT1("Failed to write a log whilst syncing the hive\n"); #if !defined(CMLIB_HOST) && !defined(_BLDR_) IoSetThreadHardErrorMode(HardErrors); #endif return FALSE; } } /* Update the primary hive file */ if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY)) { DPRINT1("Failed to write the primary hive\n"); #if !defined(CMLIB_HOST) && !defined(_BLDR_) IoSetThreadHardErrorMode(HardErrors); #endif return FALSE; } /* Update the alternate hive file if present */ if (RegistryHive->Alternate) { if (!HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_ALTERNATE)) { DPRINT1("Failed to write the alternate hive\n"); #if !defined(CMLIB_HOST) && !defined(_BLDR_) IoSetThreadHardErrorMode(HardErrors); #endif return FALSE; } } /* Clear dirty bitmap. */ RtlClearAllBits(&RegistryHive->DirtyVector); RegistryHive->DirtyCount = 0; #if !defined(CMLIB_HOST) && !defined(_BLDR_) IoSetThreadHardErrorMode(HardErrors); #endif return TRUE; } /** * @unimplemented * @brief * Determines whether a registry hive needs * to be shrinked or not based on its overall * size of the hive space to avoid unnecessary * bloat. * * @param[in] RegistryHive * A pointer to a hive descriptor where hive * shrinking is to be determined. * * @return * Returns TRUE if hive shrinking needs to be * done, FALSE otherwise. */ BOOLEAN CMAPI HvHiveWillShrink( _In_ PHHIVE RegistryHive) { /* No shrinking yet */ UNIMPLEMENTED_ONCE; return FALSE; } /** * @brief * Writes data to a registry hive. Unlike * HvSyncHive, this function just writes * the wholy registry data to a primary hive, * ignoring if a certain data block is dirty * or not. * * @param[in] RegistryHive * A pointer to a hive descriptor where data * is be written into. * * @return * Returns TRUE if hive writing has succeeded, * FALSE otherwise. */ BOOLEAN CMAPI HvWriteHive( _In_ PHHIVE RegistryHive) { ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); #if !defined(_BLDR_) /* Update hive header modification time */ KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); #endif /* Update hive file */ if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_PRIMARY)) { DPRINT1("Failed to write the hive\n"); return FALSE; } return TRUE; } /** * @brief * Writes data to an alternate registry hive. * An alternate hive is usually backed up by a primary * hive. This function is tipically used to force write * data into the alternate hive if both hives no longer match. * * @param[in] RegistryHive * A pointer to a hive descriptor where data * is to be written into. * * @return * Returns TRUE if hive writing has succeeded, * FALSE otherwise. */ BOOLEAN CMAPI HvWriteAlternateHive( _In_ PHHIVE RegistryHive) { ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); ASSERT(RegistryHive->Alternate); #if !defined(_BLDR_) /* Update hive header modification time */ KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); #endif /* Update hive file */ if (!HvpWriteHive(RegistryHive, FALSE, HFILE_TYPE_ALTERNATE)) { DPRINT1("Failed to write the alternate hive\n"); return FALSE; } return TRUE; } /** * @brief * Synchronizes a hive with recovered * data during a healing/resuscitation * operation of the registry. * * @param[in] RegistryHive * A pointer to a hive descriptor where data * syncing is to be done. * * @return * Returns TRUE if hive syncing during recovery * succeeded, FALSE otherwise. */ BOOLEAN CMAPI HvSyncHiveFromRecover( _In_ PHHIVE RegistryHive) { ASSERT(!RegistryHive->ReadOnly); ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); /* Call the private API call to do the deed for us */ return HvpWriteHive(RegistryHive, TRUE, HFILE_TYPE_PRIMARY); } /* EOF */