diff --git a/sdk/lib/cmlib/cmlib.h b/sdk/lib/cmlib/cmlib.h index 77911739ab6..8a7c13c6f15 100644 --- a/sdk/lib/cmlib/cmlib.h +++ b/sdk/lib/cmlib/cmlib.h @@ -509,6 +509,11 @@ BOOLEAN CMAPI HvWriteHive( PHHIVE RegistryHive); +BOOLEAN +CMAPI +HvSyncHiveFromRecover( + _In_ PHHIVE RegistryHive); + BOOLEAN CMAPI HvTrackCellRef( @@ -546,6 +551,11 @@ ULONG CMAPI HvpHiveHeaderChecksum( PHBASE_BLOCK HiveHeader); +BOOLEAN CMAPI +HvpVerifyHiveHeader( + _In_ PHBASE_BLOCK BaseBlock, + _In_ ULONG FileType); + // // Registry Self-Heal Routines // diff --git a/sdk/lib/cmlib/hiveinit.c b/sdk/lib/cmlib/hiveinit.c index 3eee62d149e..3ec381695ed 100644 --- a/sdk/lib/cmlib/hiveinit.c +++ b/sdk/lib/cmlib/hiveinit.c @@ -1,27 +1,59 @@ /* - * PROJECT: Registry manipulation library - * LICENSE: GPL - See COPYING in the top level directory - * COPYRIGHT: Copyright 2005 Filip Navara - * Copyright 2001 - 2005 Eric Kohl + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Configuration Manager Library - Registry Hive Loading & Initialization + * 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 +/* ENUMERATIONS *************************************************************/ + +typedef enum _RESULT +{ + NotHive, + Fail, + NoMemory, + HiveSuccess, + RecoverHeader, + RecoverData, + SelfHeal +} RESULT; + +/* PRIVATE FUNCTIONS ********************************************************/ + /** - * @name HvpVerifyHiveHeader + * @brief + * Validates the base block header of a registry + * file (hive or log). * - * Internal function to verify that a hive header has valid format. + * @param[in] BaseBlock + * A pointer to a base block header to + * be validated. + * + * @param[in] FileType + * The file type of a registry file to check + * against the file type of the base block. + * + * @return + * Returns TRUE if the base block header is valid, + * FALSE otherwise. */ -BOOLEAN CMAPI +BOOLEAN +CMAPI HvpVerifyHiveHeader( - IN PHBASE_BLOCK BaseBlock) + _In_ PHBASE_BLOCK BaseBlock, + _In_ ULONG FileType) { if (BaseBlock->Signature != HV_HBLOCK_SIGNATURE || BaseBlock->Major != HSYS_MAJOR || BaseBlock->Minor < HSYS_MINOR || - BaseBlock->Type != HFILE_TYPE_PRIMARY || + BaseBlock->Type != FileType || BaseBlock->Format != HBASE_FORMAT_MEMORY || BaseBlock->Cluster != 1 || BaseBlock->Sequence1 != BaseBlock->Sequence2 || @@ -31,7 +63,7 @@ HvpVerifyHiveHeader( DPRINT1(" Signature: 0x%x, expected 0x%x; Major: 0x%x, expected 0x%x\n", BaseBlock->Signature, HV_HBLOCK_SIGNATURE, BaseBlock->Major, HSYS_MAJOR); DPRINT1(" Minor: 0x%x expected to be >= 0x%x; Type: 0x%x, expected 0x%x\n", - BaseBlock->Minor, HSYS_MINOR, BaseBlock->Type, HFILE_TYPE_PRIMARY); + BaseBlock->Minor, HSYS_MINOR, BaseBlock->Type, FileType); DPRINT1(" Format: 0x%x, expected 0x%x; Cluster: 0x%x, expected 1\n", BaseBlock->Format, HBASE_FORMAT_MEMORY, BaseBlock->Cluster); DPRINT1(" Sequence: 0x%x, expected 0x%x; Checksum: 0x%x, expected 0x%x\n", @@ -45,13 +77,18 @@ HvpVerifyHiveHeader( } /** - * @name HvpFreeHiveBins + * @brief + * Frees all the bins within storage space + * associated with a hive descriptor. * - * Internal function to free all bin storage associated with a hive descriptor. + * @param[in] Hive + * A pointer to a hive descriptor where + * all the bins are to be freed. */ -VOID CMAPI +VOID +CMAPI HvpFreeHiveBins( - PHHIVE Hive) + _In_ PHHIVE Hive) { ULONG i; PHBIN Bin; @@ -79,15 +116,34 @@ HvpFreeHiveBins( } /** - * @name HvpAllocBaseBlockAligned + * @brief + * Allocates a cluster-aligned hive base header block. * - * Internal helper function to allocate cluster-aligned hive base blocks. + * @param[in] Hive + * A pointer to a hive descriptor where + * the header block allocator function is to + * be gathered from. + * + * @param[in] Paged + * If set to TRUE, the allocated base block will reside + * in paged pool, otherwise it will reside in non paged + * pool. + * + * @param[in] Tag + * A tag name to supply for the allocated memory block + * for identification. This is for debugging purposes. + * + * @return + * Returns an allocated base block header if the function + * succeeds, otherwise it returns NULL. */ -static __inline PHBASE_BLOCK +static +__inline +PHBASE_BLOCK HvpAllocBaseBlockAligned( - IN PHHIVE Hive, - IN BOOLEAN Paged, - IN ULONG Tag) + _In_ PHHIVE Hive, + _In_ BOOLEAN Paged, + _In_ ULONG Tag) { PHBASE_BLOCK BaseBlock; ULONG Alignment; @@ -114,16 +170,25 @@ HvpAllocBaseBlockAligned( } /** - * @name HvpInitFileName + * @brief + * Initializes a NULL-terminated Unicode hive file name + * of a hive header by copying the last 31 characters of + * the hive file name. Mainly used for debugging purposes. * - * Internal function to initialize the UNICODE NULL-terminated hive file name - * member of a hive header by copying the last 31 characters of the file name. - * Mainly used for debugging purposes. + * @param[in,out] BaseBlock + * A pointer to a base block header where the hive + * file name is to be copied to. + * + * @param[in] FileName + * A pointer to a Unicode string structure containing + * the hive file name to be copied from. If this argument + * is NULL, the base block will not have any hive file name. */ -static VOID +static +VOID HvpInitFileName( - IN OUT PHBASE_BLOCK BaseBlock, - IN PCUNICODE_STRING FileName OPTIONAL) + _Inout_ PHBASE_BLOCK BaseBlock, + _In_opt_ PCUNICODE_STRING FileName) { ULONG_PTR Offset; SIZE_T Length; @@ -149,17 +214,30 @@ HvpInitFileName( } /** - * @name HvpCreateHive + * @brief + * Initializes a hive descriptor structure for a + * newly created hive in memory. * - * Internal helper function to initialize a hive descriptor structure - * for a newly created hive in memory. + * @param[in,out] RegistryHive + * A pointer to a registry hive descriptor where + * the internal structures field are to be initialized + * for the said hive. * - * @see HvInitialize + * @param[in] FileName + * A pointer to a Unicode string structure containing + * the hive file name to be copied from. If this argument + * is NULL, the base block will not have any hive file name. + * + * @return + * Returns STATUS_SUCCESS if the function has created the + * hive descriptor successfully. STATUS_NO_MEMORY is returned + * if the base header block could not be allocated. */ -NTSTATUS CMAPI +NTSTATUS +CMAPI HvpCreateHive( - IN OUT PHHIVE RegistryHive, - IN PCUNICODE_STRING FileName OPTIONAL) + _Inout_ PHHIVE RegistryHive, + _In_opt_ PCUNICODE_STRING FileName) { PHBASE_BLOCK BaseBlock; ULONG Index; @@ -191,7 +269,7 @@ HvpCreateHive( BaseBlock->CheckSum = 0; /* Set default boot type */ - BaseBlock->BootType = 0; + BaseBlock->BootType = HBOOT_TYPE_REGULAR; /* Setup hive data */ RegistryHive->BaseBlock = BaseBlock; @@ -209,19 +287,38 @@ HvpCreateHive( } /** - * @name HvpInitializeMemoryHive + * @brief + * Initializes a hive descriptor from an already loaded + * registry hive stored in memory. The data of the hive is + * copied and it is prepared for read/write access. * - * Internal helper function to initialize hive descriptor structure for - * an existing hive stored in memory. The data of the hive is copied - * and it is prepared for read/write access. + * @param[in] Hive + * A pointer to a registry hive descriptor where + * the internal structures field are to be initialized + * from hive data that is already loaded in memory. * - * @see HvInitialize + * @param[in] ChunkBase + * A pointer to a valid base block header containing + * registry header data for initialization. + * + * @param[in] FileName + * A pointer to a Unicode string structure containing + * the hive file name to be copied from. If this argument + * is NULL, the base block will not have any hive file name. + * + * @return + * Returns STATUS_SUCCESS if the function has initialized the + * hive descriptor successfully. STATUS_REGISTRY_CORRUPT is + * returned if the base block header contains invalid header + * data. STATUS_NO_MEMORY is returned if memory could not + * be allocated for registry stuff. */ -NTSTATUS CMAPI +NTSTATUS +CMAPI HvpInitializeMemoryHive( - PHHIVE Hive, - PHBASE_BLOCK ChunkBase, - IN PCUNICODE_STRING FileName OPTIONAL) + _In_ PHHIVE Hive, + _In_ PHBASE_BLOCK ChunkBase, + _In_opt_ PCUNICODE_STRING FileName) { SIZE_T BlockIndex; PHBIN Bin, NewBin; @@ -234,7 +331,7 @@ HvpInitializeMemoryHive( DPRINT("ChunkSize: %zx\n", ChunkSize); if (ChunkSize < sizeof(HBASE_BLOCK) || - !HvpVerifyHiveHeader(ChunkBase)) + !HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY)) { DPRINT1("Registry is corrupt: ChunkSize 0x%zx < sizeof(HBASE_BLOCK) 0x%zx, " "or HvpVerifyHiveHeader() failed\n", ChunkSize, sizeof(HBASE_BLOCK)); @@ -332,20 +429,34 @@ HvpInitializeMemoryHive( } /** - * @name HvpInitializeFlatHive + * @brief + * Initializes a hive descriptor for an already loaded hive + * that is stored in memory. This descriptor serves to denote + * such hive as being "flat", that is, the data and properties + * can be only read and not written into. * - * Internal helper function to initialize hive descriptor structure for - * a hive stored in memory. The in-memory data of the hive are directly - * used and it is read-only accessible. + * @param[in] Hive + * A pointer to a registry hive descriptor where + * the internal structures fields are to be initialized + * from hive data that is already loaded in memory. Such + * hive descriptor will become read-only and flat. * - * @see HvInitialize + * @param[in] ChunkBase + * A pointer to a valid base block header containing + * registry header data for initialization. + * + * @return + * Returns STATUS_SUCCESS if the function has initialized the + * flat hive descriptor. STATUS_REGISTRY_CORRUPT is returned if + * the base block header contains invalid header data. */ -NTSTATUS CMAPI +NTSTATUS +CMAPI HvpInitializeFlatHive( - PHHIVE Hive, - PHBASE_BLOCK ChunkBase) + _In_ PHHIVE Hive, + _In_ PHBASE_BLOCK ChunkBase) { - if (!HvpVerifyHiveHeader(ChunkBase)) + if (!HvpVerifyHiveHeader(ChunkBase, HFILE_TYPE_PRIMARY)) return STATUS_REGISTRY_CORRUPT; /* Setup hive data */ @@ -357,53 +468,150 @@ HvpInitializeFlatHive( Hive->StorageTypeCount = 1; /* Set default boot type */ - ChunkBase->BootType = 0; + ChunkBase->BootType = HBOOT_TYPE_REGULAR; return STATUS_SUCCESS; } -typedef enum _RESULT -{ - NotHive, - Fail, - NoMemory, - HiveSuccess, - RecoverHeader, - RecoverData, - SelfHeal -} RESULT; - -RESULT CMAPI -HvpGetHiveHeader(IN PHHIVE Hive, - IN PHBASE_BLOCK *HiveBaseBlock, - IN PLARGE_INTEGER TimeStamp) +/** + * @brief + * Retrieves the base block hive header from the + * primary hive file stored in physical backing storage. + * This function may invoke a self-healing warning if + * hive header couldn't be obtained. See Return and Remarks + * sections for further information. + * + * @param[in] Hive + * A pointer to a registry hive descriptor that points + * to the primary hive being loaded. This descriptor is + * needed to obtain the hive header block from said hive. + * + * @param[in,out] HiveBaseBlock + * A pointer returned by the function that contains + * the hive header base block buffer obtained from + * the primary hive file pointed by the Hive argument. + * This parameter must not be NULL! + * + * @param[in,out] TimeStamp + * A pointer returned by the function that contains + * the time-stamp of the registry hive file at the + * moment of creation or modification. This parameter + * must not be NULL! + * + * @return + * This function returns a result indicator. That is, + * HiveSuccess is returned if the hive header was obtained + * successfully. NoMemory is returned if the hive base block + * could not be allocated. NotHive is returned if the hive file + * that's been read isn't actually a hive. RecoverHeader is + * returned if the header needs to be recovered. RecoverData + * is returned if the hive data needs to be returned. + * + * @remarks + * RecoverHeader and RecoverData are status indicators that + * invoke a self-healing procedure if the hive header could not + * be obtained in a normal way and as a matter of fact the whole + * registry initialization procedure is orchestrated. RecoverHeader + * implies that the base block header of a hive is corrupt and it + * needs to be recovered, whereas RecoverData implies the registry + * data is corrupt. The latter status indicator is less severe unlike + * the former because the system can cope with data loss. + */ +RESULT +CMAPI +HvpGetHiveHeader( + _In_ PHHIVE Hive, + _Inout_ PHBASE_BLOCK *HiveBaseBlock, + _Inout_ PLARGE_INTEGER TimeStamp) { PHBASE_BLOCK BaseBlock; ULONG Result; - ULONG Offset = 0; + ULONG FileOffset; + PHBIN FirstBin; ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); /* Assume failure and allocate the base block */ *HiveBaseBlock = NULL; BaseBlock = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM); - if (!BaseBlock) return NoMemory; + if (!BaseBlock) + { + DPRINT1("Failed to allocate an aligned base block buffer\n"); + return NoMemory; + } /* Clear it */ RtlZeroMemory(BaseBlock, sizeof(HBASE_BLOCK)); /* Now read it from disk */ + FileOffset = 0; Result = Hive->FileRead(Hive, HFILE_TYPE_PRIMARY, - &Offset, + &FileOffset, BaseBlock, Hive->Cluster * HSECTOR_SIZE); + if (!Result) + { + /* + * Don't assume the hive is ultimately destroyed + * but instead try to read the first block of + * the first bin hive. So that we're sure of + * ourselves we can somewhat recover this hive. + */ + FileOffset = HBLOCK_SIZE; + Result = Hive->FileRead(Hive, + HFILE_TYPE_PRIMARY, + &FileOffset, + (PVOID)BaseBlock, + Hive->Cluster * HSECTOR_SIZE); + if (!Result) + { + DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n"); + Hive->Free(BaseBlock, Hive->BaseBlockAlloc); + return NotHive; + } - /* Couldn't read: assume it's not a hive */ - if (!Result) return NotHive; + /* + * Deconstruct the casted buffer we got + * into a hive bin. Check if the offset + * position is in the right place (namely + * its offset must be 0 because it's the first + * bin) and it should have a sane signature. + */ + FirstBin = (PHBIN)BaseBlock; + if (FirstBin->Signature != HV_HBIN_SIGNATURE || + FirstBin->FileOffset != 0) + { + DPRINT1("Failed to read the first block of the first bin hive (hive too corrupt)\n"); + Hive->Free(BaseBlock, Hive->BaseBlockAlloc); + return NotHive; + } - /* Do validation */ - if (!HvpVerifyHiveHeader(BaseBlock)) return NotHive; + /* + * There's still hope for this hive so acknowledge the + * caller this hive needs a recoverable header. + */ + *TimeStamp = BaseBlock->TimeStamp; + DPRINT1("The hive is not fully corrupt, the base block needs to be RECOVERED\n"); + return RecoverHeader; + } + + /* + * This hive has a base block that's not maimed + * but is the header data valid? + * + * FIXME: We must check if primary and secondary + * sequences mismatch separately and fire up RecoverData + * in that case but due to a hack in HvLoadHive, let + * HvpVerifyHiveHeader check the sequences for now. + */ + if (!HvpVerifyHiveHeader(BaseBlock, HFILE_TYPE_PRIMARY)) + { + DPRINT1("The hive base header block needs to be RECOVERED\n"); + *TimeStamp = BaseBlock->TimeStamp; + Hive->Free(BaseBlock, Hive->BaseBlockAlloc); + return RecoverHeader; + } /* Return information */ *HiveBaseBlock = BaseBlock; @@ -411,17 +619,391 @@ HvpGetHiveHeader(IN PHHIVE Hive, return HiveSuccess; } -NTSTATUS CMAPI -HvLoadHive(IN PHHIVE Hive, - IN PCUNICODE_STRING FileName OPTIONAL) +/** + * @brief + * Computes the hive space size by querying + * the file size of the associated hive file. + * + * @param[in] Hive + * A pointer to a hive descriptor where the + * hive length size is to be calculated. + * + * @return + * Returns the computed hive size. + */ +ULONG +CMAPI +HvpQueryHiveSize( + _In_ PHHIVE Hive) +{ +#if !defined(CMLIB_HOST) && !defined(_BLDR_) + NTSTATUS Status; + FILE_STANDARD_INFORMATION FileStandard; + IO_STATUS_BLOCK IoStatusBlock; +#endif + ULONG HiveSize = 0; + + /* + * Query the file size of the physical hive + * file. We need that information in order + * to ensure how big the hive actually is. + */ +#if !defined(CMLIB_HOST) && !defined(_BLDR_) + Status = ZwQueryInformationFile(((PCMHIVE)Hive)->FileHandles[HFILE_TYPE_PRIMARY], + &IoStatusBlock, + &FileStandard, + sizeof(FILE_STANDARD_INFORMATION), + FileStandardInformation); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ZwQueryInformationFile returned 0x%lx\n", Status); + return HiveSize; + } + + /* Now compute the hive size */ + HiveSize = FileStandard.EndOfFile.u.LowPart - HBLOCK_SIZE; +#endif + return HiveSize; +} + +/** + * @brief + * Recovers the base block header by obtaining + * it from a log file associated with the hive. + * + * @param[in] Hive + * A pointer to a hive descriptor associated + * with the log file where the hive header is + * to be read from. + * + * @param[in] TimeStamp + * A pointer to a time-stamp used to check + * if the provided time matches with that + * of the hive. + * + * @param[in,out] BaseBlock + * A pointer returned by the caller that contains + * the base block header that was read from the log. + * This base block could be also made manually by hand. + * See Remarks for further information. + * + * @return + * Returns HiveSuccess if the header was obtained + * normally from the log. NoMemory is returned if + * the base block header could not be allocated. + * Fail is returned if self-healing mode is disabled + * and the log couldn't be read or a write attempt + * to the primary hive has failed. SelfHeal is returned + * to indicate that self-heal mode goes further. + * + * @remarks + * When SelfHeal is returned this indicates that + * even the log we have gotten at hand is corrupt + * but since we do have at least a log our only hope + * is to reconstruct the pieces of the base header + * by hand. + */ +RESULT +CMAPI +HvpRecoverHeaderFromLog( + _In_ PHHIVE Hive, + _In_ PLARGE_INTEGER TimeStamp, + _Inout_ PHBASE_BLOCK *BaseBlock) +{ + BOOLEAN Success; + PHBASE_BLOCK LogHeader; + ULONG FileOffset; + ULONG HiveSize; + BOOLEAN HeaderResuscitated; + + /* + * The cluster must not be greater than what the + * base block can permit. + */ + ASSERT(sizeof(HBASE_BLOCK) >= (HSECTOR_SIZE * Hive->Cluster)); + + /* Assume we haven't resuscitated the header */ + HeaderResuscitated = FALSE; + + /* Allocate an aligned buffer for the log header */ + LogHeader = HvpAllocBaseBlockAligned(Hive, TRUE, TAG_CM); + if (!LogHeader) + { + DPRINT1("Failed to allocate memory for the log header\n"); + return NoMemory; + } + + /* Zero out our header buffer */ + RtlZeroMemory(LogHeader, HSECTOR_SIZE); + + /* Get the base header from the log */ + FileOffset = 0; + Success = Hive->FileRead(Hive, + HFILE_TYPE_LOG, + &FileOffset, + LogHeader, + Hive->Cluster * HSECTOR_SIZE); + if (!Success || + !HvpVerifyHiveHeader(LogHeader, HFILE_TYPE_LOG) || + TimeStamp->HighPart != LogHeader->TimeStamp.HighPart || + TimeStamp->LowPart != LogHeader->TimeStamp.LowPart) + { + /* + * We failed to read the base block header from + * the log, or the header itself or timestamp is + * invalid. Check if self healing is enabled. + */ + if (!CmIsSelfHealEnabled(FALSE)) + { + DPRINT1("The log couldn't be read and self-healing mode is disabled\n"); + Hive->Free(LogHeader, Hive->BaseBlockAlloc); + return Fail; + } + + /* + * Determine the size of this hive so that + * we can estabilish the length of the base + * block we are trying to resuscitate. + */ + HiveSize = HvpQueryHiveSize(Hive); + if (HiveSize == 0) + { + DPRINT1("Failed to query the hive size\n"); + Hive->Free(LogHeader, Hive->BaseBlockAlloc); + return Fail; + } + + /* + * We can still resuscitate the base header if we + * could not grab one from the log by reconstructing + * the header internals by hand (this assumes the + * root cell is not NIL nor damaged). CmCheckRegistry + * does the ultimate judgement whether the root cell + * is fatally kaput or not after the hive has been + * initialized and loaded. + * + * For more information about base block header + * resuscitation, see https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md#notes-4. + */ + LogHeader->Signature = HV_HBLOCK_SIGNATURE; + LogHeader->Sequence1 = 1; + LogHeader->Sequence2 = 1; + LogHeader->Cluster = 1; + LogHeader->Length = HiveSize; + LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader); + + /* + * Acknowledge that we have resuscitated + * the header. + */ + HeaderResuscitated = TRUE; + DPRINT1("Header has been resuscitated, triggering self-heal mode\n"); + } + + /* + * Tag this log header as a primary hive before + * writing it to the hive. + */ + LogHeader->Type = HFILE_TYPE_PRIMARY; + + /* + * If we have not made attempts of recovering + * the header due to log corruption then we + * have to compute the checksum. This is + * already done when the header has been resuscitated + * so don't try to do it twice. + */ + if (!HeaderResuscitated) + { + LogHeader->CheckSum = HvpHiveHeaderChecksum(LogHeader); + } + + /* Write the header back to hive now */ + Success = Hive->FileWrite(Hive, + HFILE_TYPE_PRIMARY, + &FileOffset, + LogHeader, + Hive->Cluster * HSECTOR_SIZE); + if (!Success) + { + DPRINT1("Couldn't write the base header to primary hive\n"); + Hive->Free(LogHeader, Hive->BaseBlockAlloc); + return Fail; + } + + *BaseBlock = LogHeader; + return HeaderResuscitated ? SelfHeal : HiveSuccess; +} + +/** + * @brief + * Recovers the registry data by obtaining it + * from a log that is associated with the hive. + * + * @param[in] Hive + * A pointer to a hive descriptor associated + * with the log file where the hive data is to + * be read from. + * + * @param[in] BaseBlock + * A pointer to a base block header. + * + * @return + * Returns HiveSuccess if the data was obtained + * normally from the log. Fail is returned if + * self-healing is disabled and we couldn't be + * able to read the data from the log or the + * dirty vector signature is garbage or we + * failed to write the data block to the primary + * hive. SelfHeal is returned to indicate that + * the log is corrupt and the system will continue + * to be recovered at the expense of data loss. + */ +RESULT +CMAPI +HvpRecoverDataFromLog( + _In_ PHHIVE Hive, + _In_ PHBASE_BLOCK BaseBlock) +{ + BOOLEAN Success; + ULONG FileOffset; + ULONG BlockIndex; + ULONG LogIndex; + ULONG StorageLength; + UCHAR DirtyVector[HSECTOR_SIZE]; + UCHAR Buffer[HBLOCK_SIZE]; + + /* Read the dirty data from the log */ + FileOffset = HV_LOG_HEADER_SIZE; + Success = Hive->FileRead(Hive, + HFILE_TYPE_LOG, + &FileOffset, + DirtyVector, + HSECTOR_SIZE); + if (!Success) + { + if (!CmIsSelfHealEnabled(FALSE)) + { + DPRINT1("The log couldn't be read and self-healing mode is disabled\n"); + return Fail; + } + + /* + * There's nothing we can do on a situation + * where dirty data could not be read from + * the log. It does not make much sense to + * behead the system on such scenario so + * trigger a self-heal and go on. The worst + * thing that can happen? Data loss, that's it. + */ + DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n"); + return SelfHeal; + } + + /* Check the dirty vector */ + if (*((PULONG)DirtyVector) != HV_LOG_DIRTY_SIGNATURE) + { + if (!CmIsSelfHealEnabled(FALSE)) + { + DPRINT1("The log's dirty vector signature is not valid\n"); + return Fail; + } + + /* + * Trigger a self-heal like above. If the + * vector signature is garbage then logically + * whatever comes after the signature is also + * garbage. + */ + DPRINT1("Triggering self-heal mode, DATA LOSS IS IMMINENT\n"); + return SelfHeal; + } + + /* Now read each data individually and write it back to hive */ + LogIndex = 0; + StorageLength = BaseBlock->Length / HBLOCK_SIZE; + for (BlockIndex = 0; BlockIndex < StorageLength; BlockIndex++) + { + /* Skip this block if it's not dirty and go to the next one */ + if (DirtyVector[BlockIndex + sizeof(HV_LOG_DIRTY_SIGNATURE)] != HV_LOG_DIRTY_BLOCK) + { + continue; + } + + FileOffset = HSECTOR_SIZE + HSECTOR_SIZE + LogIndex * HBLOCK_SIZE; + Success = Hive->FileRead(Hive, + HFILE_TYPE_LOG, + &FileOffset, + Buffer, + HBLOCK_SIZE); + if (!Success) + { + DPRINT1("Failed to read the dirty block (index %lu)\n", BlockIndex); + return Fail; + } + + FileOffset = HBLOCK_SIZE + BlockIndex * HBLOCK_SIZE; + Success = Hive->FileWrite(Hive, + HFILE_TYPE_PRIMARY, + &FileOffset, + Buffer, + HBLOCK_SIZE); + if (!Success) + { + DPRINT1("Failed to write dirty block to hive (index %lu)\n", BlockIndex); + return Fail; + } + + /* Increment the index in log as we continue further */ + LogIndex++; + } + + return HiveSuccess; +} + +/** + * @brief + * Loads a registry hive from a physical hive file + * within the physical backing storage. Base block + * and registry data are read from the said physical + * hive file. This function can perform registry recovery + * if hive loading could not be done normally. + * + * @param[in] Hive + * A pointer to a hive descriptor where the said hive + * is to be loaded from the physical hive file. + * + * @param[in] FileName + * A pointer to a NULL-terminated Unicode string structure + * containing the hive file name to be copied from. + * + * @return + * STATUS_SUCCESS is returned if the hive has been loaded + * successfully. STATUS_INSUFFICIENT_RESOURCES is returned + * if there's not enough memory resources to satisfy registry + * operations and/or requests. STATUS_NOT_REGISTRY_FILE is returned + * if the hive is not actually a hive file. STATUS_REGISTRY_CORRUPT + * is returned if the hive has subdued previous damage and + * the hive could not be recovered because there's no + * log present or self healing is disabled. STATUS_REGISTRY_RECOVERED + * is returned if the hive has been recovered. An eventual flush + * of the registry is needed after the hive's been fully loaded. + */ +NTSTATUS +CMAPI +HvLoadHive( + _In_ PHHIVE Hive, + _In_opt_ PCUNICODE_STRING FileName) { NTSTATUS Status; + BOOLEAN Success; PHBASE_BLOCK BaseBlock = NULL; - ULONG Result; + ULONG Result, Result2; LARGE_INTEGER TimeStamp; ULONG Offset = 0; PVOID HiveData; ULONG FileSize; + BOOLEAN HiveSelfHeal = FALSE; /* Get the hive header */ Result = HvpGetHiveHeader(Hive, &BaseBlock, &TimeStamp); @@ -429,26 +1011,91 @@ HvLoadHive(IN PHHIVE Hive, { /* Out of memory */ case NoMemory: - + { /* Fail */ + DPRINT1("There's no enough memory to get the header\n"); return STATUS_INSUFFICIENT_RESOURCES; + } /* Not a hive */ case NotHive: - + { /* Fail */ + DPRINT1("The hive is not an actual registry hive file\n"); return STATUS_NOT_REGISTRY_FILE; + } - /* Has recovery data */ + /* Hive data needs a repair */ case RecoverData: - case RecoverHeader: + { + /* + * FIXME: We must be handling this status + * case if the header isn't corrupt but + * the counter sequences do not match but + * due to a hack in HvLoadHive we have + * to do both a header + data recovery. + * RecoverHeader also implies RecoverData + * anyway. When HvLoadHive gets rid of + * that hack, data recovery must be done + * after we read the hive block by block. + */ + break; + } - /* Fail */ - return STATUS_REGISTRY_CORRUPT; + /* Hive header needs a repair */ + case RecoverHeader: + { + /* Check if this hive has a log at hand to begin with */ + #if (NTDDI_VERSION < NTDDI_VISTA) + if (!Hive->Log) + { + DPRINT1("The hive has no log for header recovery\n"); + return STATUS_REGISTRY_CORRUPT; + } + #endif + + /* The header needs to be recovered so do it */ + DPRINT1("Attempting to heal the header...\n"); + Result2 = HvpRecoverHeaderFromLog(Hive, &TimeStamp, &BaseBlock); + if (Result2 == NoMemory) + { + DPRINT1("There's no enough memory to recover header from log\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Did we fail? */ + if (Result2 == Fail) + { + DPRINT1("Failed to recover the hive header\n"); + return STATUS_REGISTRY_CORRUPT; + } + + /* Did we trigger the self-heal mode? */ + if (Result2 == SelfHeal) + { + HiveSelfHeal = TRUE; + } + + /* Now recover the data */ + Result2 = HvpRecoverDataFromLog(Hive, BaseBlock); + if (Result2 == Fail) + { + DPRINT1("Failed to recover the hive data\n"); + return STATUS_REGISTRY_CORRUPT; + } + + /* Tag the boot as self heal if we haven't done it before */ + if ((Result2 == SelfHeal) && (!HiveSelfHeal)) + { + HiveSelfHeal = TRUE; + } + + break; + } } - /* Set default boot type */ - BaseBlock->BootType = 0; + /* Set the boot type */ + BaseBlock->BootType = HiveSelfHeal ? HBOOT_TYPE_SELF_HEAL : HBOOT_TYPE_REGULAR; /* Setup hive data */ Hive->BaseBlock = BaseBlock; @@ -460,79 +1107,214 @@ HvLoadHive(IN PHHIVE Hive, if (!HiveData) { Hive->Free(BaseBlock, Hive->BaseBlockAlloc); + DPRINT1("There's no enough memory to allocate hive data\n"); return STATUS_INSUFFICIENT_RESOURCES; } - /* Now read the whole hive */ - Result = Hive->FileRead(Hive, - HFILE_TYPE_PRIMARY, - &Offset, - HiveData, - FileSize); - if (!Result) + /* HACK (see explanation below): Now read the whole hive */ + Success = Hive->FileRead(Hive, + HFILE_TYPE_PRIMARY, + &Offset, + HiveData, + FileSize); + if (!Success) { + DPRINT1("Failed to read the whole hive\n"); Hive->Free(HiveData, FileSize); Hive->Free(BaseBlock, Hive->BaseBlockAlloc); return STATUS_NOT_REGISTRY_FILE; } - // This is a HACK! - /* Free our base block... it's usless in this implementation */ + /* + * HACK (FIXME): Free our base block... it's useless in + * this implementation. + * + * And it's useless because while the idea of reading the + * hive from physical file is correct, the implementation + * is hacky and incorrect. Instead of reading the whole hive, + * we should be instead reading the hive block by block, + * deconstruct the block buffer and enlist the bins and + * prepare the storage for the hive. What we currently do + * is we try to initialize the hive storage and bins enlistment + * by calling HvpInitializeMemoryHive below. This mixes + * HINIT_FILE and HINIT_MEMORY together which is disgusting + * because HINIT_FILE implementation shouldn't be calling + * HvpInitializeMemoryHive. + */ Hive->Free(BaseBlock, Hive->BaseBlockAlloc); - - /* Initialize the hive directly from memory */ Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); if (!NT_SUCCESS(Status)) + { + DPRINT1("Failed to initialize hive from memory\n"); Hive->Free(HiveData, FileSize); + return Status; + } - return Status; + /* + * If we have done some sort of recovery against + * the hive we were going to load it from file, + * tell the caller we did recover it. The caller + * is responsible to flush the data later on. + */ + return (Result == RecoverHeader) ? STATUS_REGISTRY_RECOVERED : STATUS_SUCCESS; } /** - * @name HvInitialize + * @brief + * Initializes a registry hive. It allocates a hive + * descriptor and sets up the hive type depending + * on the type chosen by the caller. * - * Allocate a new hive descriptor structure and intialize it. + * @param[in,out] RegistryHive + * A pointer to a hive descriptor to be initialized. * - * @param RegistryHive - * Output variable to store pointer to the hive descriptor. - * @param OperationType - * - HV_OPERATION_CREATE_HIVE - * Create a new hive for read/write access. - * - HV_OPERATION_MEMORY - * Load and copy in-memory hive for read/write access. The - * pointer to data passed to this routine can be freed after - * the function is executed. - * - HV_OPERATION_MEMORY_INPLACE - * Load an in-memory hive for read-only access. The pointer - * to data passed to this routine MUSTN'T be freed until - * HvFree is called. - * @param ChunkBase - * Pointer to hive data. - * @param ChunkSize - * Size of passed hive data. + * @param[in] OperationType + * The operation type to choose for hive initialization. + * For further information about this, see Remarks. + * + * @param[in] HiveFlags + * A hive flag. Such flag is used to determine what kind + * of action must be taken into the hive or what aspects + * must be taken into account for such hive. For further + * information, see Remarks. + * + * @param[in] FileType + * Hive file type. For the newly initialized hive, you can + * choose from three different types for the hive: + * + * HFILE_TYPE_PRIMARY - Initializes a hive as primary hive + * of the system. + * + * HFILE_TYPE_LOG - The newly created hive is a hive log. + * Logs don't exist per se but they're accompanied with their + * associated primary hives. The Log field member of the hive + * descriptor is set to TRUE. + * + * HFILE_TYPE_EXTERNAL - The newly created hive is a portable + * hive, that can be used and copied for different machines, + * unlike primary hives. + * + * HFILE_TYPE_ALTERNATE - The newly created hive is an alternate hive. + * Technically speaking it is the same as a primary hive (the representation + * of on-disk image of the registry header is HFILE_TYPE_PRIMARY), with + * the purpose is to serve as a backup hive. The Alternate field of the + * hive descriptor is set to TRUE. Only the SYSTEM hive has a backup + * alternate hive. + * + * @param[in] HiveData + * An arbitrary pointer that points to the hive data. Usually this + * data is in form of a hive base block given by the caller of this + * function. + * + * @param[in] Allocate + * A pointer to a ALLOCATE_ROUTINE function that describes + * the main allocation routine for this hive. This parameter + * can be NULL. + * + * @param[in] Free + * A pointer to a FREE_ROUTINE function that describes the + * the main memory freeing routine for this hive. This parameter + * can be NULL. + * + * @param[in] FileSetSize + * A pointer to a FILE_SET_SIZE_ROUTINE function that describes + * the file set size routine for this hive. This parameter + * can be NULL. + * + * @param[in] FileWrite + * A pointer to a FILE_WRITE_ROUTINE function that describes + * the file writing routine for this hive. This parameter + * can be NULL. + * + * @param[in] FileRead + * A pointer to a FILE_READ_ROUTINE function that describes + * the file reading routine for this hive. This parameter + * can be NULL. + * + * @param[in] FileFlush + * A pointer to a FILE_FLUSH_ROUTINE function that describes + * the file flushing routine for this hive. This parameter + * can be NULL. + * + * @param[in] Cluster + * The registry hive cluster to be set. Usually this value + * is set to 1. + * + * @param[in] FileName + * A to a NULL-terminated Unicode string structure containing + * the hive file name. This parameter can be NULL. * * @return - * STATUS_NO_MEMORY - A memory allocation failed. - * STATUS_REGISTRY_CORRUPT - Registry corruption was detected. - * STATUS_SUCCESS + * Returns STATUS_SUCCESS if the function has successfully + * initialized the hive. STATUS_REGISTRY_RECOVERED is returned + * if the hive has subdued previous damage and it's been recovered. + * This function will perform a hive writing and flushing with + * healthy and recovered data in that case. STATUS_REGISTRY_IO_FAILED + * is returned if registry hive writing/flushing of recovered data + * has failed. STATUS_INVALID_PARAMETER is returned if an invalid + * operation type pointed by OperationType parameter has been + * submitted. A failure NTSTATUS code is returned otherwise. * - * @see HvFree + * @remarks + * OperationType parameter influences how should the hive be + * initialized. These are the following supported operation + * types: + * + * HINIT_CREATE -- Creates a new fresh hive. + * + * HINIT_MEMORY -- Initializes a registry hive that already exists + * from memory. The hive data is copied from the + * loaded hive in memory and used for read/write + * access. + * + * HINIT_FLAT -- Initializes a flat registry hive, with data that can + * only be read and not written into. Cells are always + * allocated on a flat hive. + * + * HINIT_FILE -- Initializes a hive from a hive file from the physical + * backing storage of the system. In this situation the + * function will perform self-healing and resuscitation + * procedures if data read from the physical hive file + * is corrupt. + * + * HINIT_MEMORY_INPLACE -- This operation type is similar to HINIT_FLAT, + * with the difference is that the hive is initialized + * with hive data from memory. The hive can only be read + * and not written into. + * + * HINIT_MAPFILE -- Initializes a hive from a hive file from the physical + * backing storage of the system. Unlike HINIT_FILE, the + * initialized hive is not backed to paged pool in memory + * but rather through mapping views. + * + * Alongside the operation type, the hive flags also influence the aspect + * of the newly initialized hive. These are the following supported hive + * flags: + * + * HIVE_VOLATILE -- Tells the function that this hive will be volatile, that + * is, the data stored inside the hive space resides only + * in volatile memory of the system, aka the RAM, and the + * data will be erased upon shutdown of the system. + * + * HIVE_NOLAZYFLUSH -- Tells the function that no lazy flushing must be + * done to this hive. */ -NTSTATUS CMAPI +NTSTATUS +CMAPI HvInitialize( - PHHIVE RegistryHive, - ULONG OperationType, - ULONG HiveFlags, - ULONG FileType, - PVOID HiveData OPTIONAL, - PALLOCATE_ROUTINE Allocate, - PFREE_ROUTINE Free, - PFILE_SET_SIZE_ROUTINE FileSetSize, - PFILE_WRITE_ROUTINE FileWrite, - PFILE_READ_ROUTINE FileRead, - PFILE_FLUSH_ROUTINE FileFlush, - ULONG Cluster OPTIONAL, - PCUNICODE_STRING FileName OPTIONAL) + _Inout_ PHHIVE RegistryHive, + _In_ ULONG OperationType, + _In_ ULONG HiveFlags, + _In_ ULONG FileType, + _In_opt_ PVOID HiveData, + _In_opt_ PALLOCATE_ROUTINE Allocate, + _In_opt_ PFREE_ROUTINE Free, + _In_opt_ PFILE_SET_SIZE_ROUTINE FileSetSize, + _In_opt_ PFILE_WRITE_ROUTINE FileWrite, + _In_opt_ PFILE_READ_ROUTINE FileRead, + _In_opt_ PFILE_FLUSH_ROUTINE FileFlush, + _In_ ULONG Cluster, + _In_opt_ PCUNICODE_STRING FileName) { NTSTATUS Status; PHHIVE Hive = RegistryHive; @@ -570,42 +1352,95 @@ HvInitialize( switch (OperationType) { case HINIT_CREATE: + { + /* Create a new fresh hive */ Status = HvpCreateHive(Hive, FileName); break; + } case HINIT_MEMORY: + { + /* Initialize a hive from memory */ Status = HvpInitializeMemoryHive(Hive, HiveData, FileName); break; + } case HINIT_FLAT: + { + /* Initialize a flat read-only hive */ Status = HvpInitializeFlatHive(Hive, HiveData); break; + } case HINIT_FILE: { + /* Initialize a hive by loading it from physical file in backing storage */ Status = HvLoadHive(Hive, FileName); if ((Status != STATUS_SUCCESS) && (Status != STATUS_REGISTRY_RECOVERED)) { /* Unrecoverable failure */ + DPRINT1("Registry hive couldn't be initialized, it's corrupt (hive 0x%p)\n", Hive); return Status; } - /* Check for previous damage */ - ASSERT(Status != STATUS_REGISTRY_RECOVERED); + /* + * Check if we have recovered this hive. We are responsible to + * flush the primary hive back to backing storage afterwards. + */ + if (Status == STATUS_REGISTRY_RECOVERED) + { + if (!HvSyncHiveFromRecover(Hive)) + { + DPRINT1("Fail to write healthy data back to hive\n"); + return STATUS_REGISTRY_IO_FAILED; + } + + /* + * We are saved from hell, now clear out the + * dirty bits and dirty count. + * + * FIXME: We must as well clear out the log + * and reset its size to 0 but we are lacking + * in code that deals with log growing/shrinking + * management. When the time comes to implement + * this stuff we must set the LogSize and file size + * to 0 here. + */ + RtlClearAllBits(&Hive->DirtyVector); + Hive->DirtyCount = 0; + + /* + * Masquerade the status code as success. + * STATUS_REGISTRY_RECOVERED is not a failure + * code but not STATUS_SUCCESS either so the caller + * thinks we failed at our job. + */ + Status = STATUS_SUCCESS; + } + break; } case HINIT_MEMORY_INPLACE: + { // Status = HvpInitializeMemoryInplaceHive(Hive, HiveData); // break; + DPRINT1("HINIT_MEMORY_INPLACE is UNIMPLEMENTED\n"); + return STATUS_NOT_IMPLEMENTED; + } case HINIT_MAPFILE: + { + DPRINT1("HINIT_MAPFILE is UNIMPLEMENTED\n"); + return STATUS_NOT_IMPLEMENTED; + } default: - /* FIXME: A better return status value is needed */ - Status = STATUS_NOT_IMPLEMENTED; - ASSERT(FALSE); + { + DPRINT1("Invalid operation type (OperationType = %lu)\n", OperationType); + return STATUS_INVALID_PARAMETER; + } } if (!NT_SUCCESS(Status)) return Status; @@ -619,14 +1454,19 @@ HvInitialize( } /** - * @name HvFree + * @brief + * Frees all the bins within the storage, the dirty vector + * and the base block associated with the given registry + * hive descriptor. * - * Free all stroage and handles associated with hive descriptor. - * But do not free the hive descriptor itself. + * @param[in] RegistryHive + * A pointer to a hive descriptor where all of its data + * is to be freed. */ -VOID CMAPI +VOID +CMAPI HvFree( - PHHIVE RegistryHive) + _In_ PHHIVE RegistryHive) { if (!RegistryHive->ReadOnly) { diff --git a/sdk/lib/cmlib/hivewrt.c b/sdk/lib/cmlib/hivewrt.c index 76bf72b1b45..4fea5fa70e6 100644 --- a/sdk/lib/cmlib/hivewrt.c +++ b/sdk/lib/cmlib/hivewrt.c @@ -1,176 +1,350 @@ /* - * PROJECT: Registry manipulation library - * LICENSE: GPL - See COPYING in the top level directory - * COPYRIGHT: Copyright 2005 Filip Navara - * Copyright 2001 - 2005 Eric Kohl + * 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 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 -static BOOLEAN CMAPI -HvpWriteLog( - PHHIVE RegistryHive) +/* DECLARATIONS *************************************************************/ + +#if !defined(CMLIB_HOST) && !defined(_BLDR_) +BOOLEAN +NTAPI +IoSetThreadHardErrorMode( + _In_ BOOLEAN HardErrorEnabled); +#endif + +/* GLOBALS *****************************************************************/ + +#if !defined(CMLIB_HOST) && !defined(_BLDR_) +extern BOOLEAN CmpMiniNTBoot; +#endif + +/* 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; - UINT32 BufferSize; - UINT32 BitmapSize; - PUCHAR Buffer; - PUCHAR Ptr; ULONG BlockIndex; ULONG LastIndex; - PVOID BlockPtr; - BOOLEAN Success; - static ULONG PrintCount = 0; - - if (PrintCount++ == 0) - { - UNIMPLEMENTED; - } - return TRUE; + 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 == FALSE); ASSERT(RegistryHive->BaseBlock->Length == RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); - DPRINT("HvpWriteLog called\n"); + /* Validate the base header before we go further */ + HvpValidateBaseHeader(RegistryHive); + /* + * The sequences can diverge in an occurrence of forced + * shutdown of the system such as during a power failure, + * the hardware crapping itself or during a system crash + * 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; } - BitmapSize = RegistryHive->DirtyVector.SizeOfBitMap; - BufferSize = HV_LOG_HEADER_SIZE + sizeof(ULONG) + BitmapSize; - BufferSize = ROUND_UP(BufferSize, HBLOCK_SIZE); + /* + * 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. + */ - DPRINT("Bitmap size %u buffer size: %u\n", BitmapSize, BufferSize); + /* + * 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; - Buffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM); - if (Buffer == NULL) + /* 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; } - /* Update first update counter and CheckSum */ + /* 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); + RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); - /* Copy hive header */ - RtlCopyMemory(Buffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE); - Ptr = Buffer + HV_LOG_HEADER_SIZE; - RtlCopyMemory(Ptr, "DIRT", 4); - Ptr += 4; - RtlCopyMemory(Ptr, RegistryHive->DirtyVector.Buffer, BitmapSize); + /* Copy the base block header */ + RtlCopyMemory(HeaderBuffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE); + Ptr = HeaderBuffer + HV_LOG_HEADER_SIZE; - /* Write hive block and block bitmap */ - FileOffset = 0; - Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG, - &FileOffset, Buffer, BufferSize); - RegistryHive->Free(Buffer, 0); + /* Copy the dirty vector */ + *((PULONG)Ptr) = HV_LOG_DIRTY_SIGNATURE; + Ptr += sizeof(HV_LOG_DIRTY_SIGNATURE); - if (!Success) - { - return FALSE; - } - - /* Write dirty blocks */ - FileOffset = BufferSize; + /* + * 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 == ~0U || BlockIndex < LastIndex) + if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) { break; } - BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; + /* + * 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++; + } - /* Write hive block */ + /* 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, BlockPtr, HBLOCK_SIZE); + &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; } - Success = RegistryHive->FileSetSize(RegistryHive, HFILE_TYPE_LOG, FileOffset, FileOffset); - if (!Success) - { - DPRINT("FileSetSize failed\n"); - return FALSE; - } - - /* Flush the log file */ + /* + * 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) { - DPRINT("FileFlush failed\n"); + DPRINT1("Failed to flush the log\n"); + return FALSE; } - /* Update second update counter and CheckSum */ + /* + * 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); + RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); - /* Write hive header again with updated sequence counter. */ + /* 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 the log file */ + /* Flush it finally */ Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0); if (!Success) { - DPRINT("FileFlush failed\n"); + DPRINT1("Failed to flush the log\n"); + return FALSE; } return TRUE; } -static BOOLEAN CMAPI +/** + * @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. + * + * @return + * Returns TRUE if writing to hive has succeeded, + * FALSE otherwise. + */ +static +BOOLEAN +CMAPI HvpWriteHive( - PHHIVE RegistryHive, - BOOLEAN OnlyDirty) + _In_ PHHIVE RegistryHive, + _In_ BOOLEAN OnlyDirty) { + BOOLEAN Success; ULONG FileOffset; ULONG BlockIndex; ULONG LastIndex; - PVOID BlockPtr; - BOOLEAN Success; + PVOID Block; ASSERT(RegistryHive->ReadOnly == FALSE); ASSERT(RegistryHive->BaseBlock->Length == RegistryHive->Storage[Stable].Length * HBLOCK_SIZE); + ASSERT(RegistryHive->BaseBlock->RootCell != HCELL_NIL); - DPRINT("HvpWriteHive called\n"); + /* Validate the base header before we go further */ + HvpValidateBaseHeader(RegistryHive); + /* + * The sequences can diverge in an occurrence of forced + * shutdown of the system such as during a power failure, + * the hardware crapping itself or during a system crash + * 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 first update counter and CheckSum */ + /* + * 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); + RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Write hive block */ FileOffset = 0; @@ -179,46 +353,70 @@ HvpWriteHive( 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 syncrhonize 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 + * writing of the hive. + */ 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 == ~0U || BlockIndex < LastIndex) + if (BlockIndex == ~HV_CLEAN_BLOCK || BlockIndex < LastIndex) { break; } } - BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; + /* Get the block and offset position */ + Block = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress; FileOffset = (BlockIndex + 1) * HBLOCK_SIZE; - /* Write hive block */ + /* Now write this block to primary hive file */ Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY, - &FileOffset, BlockPtr, HBLOCK_SIZE); + &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, HFILE_TYPE_PRIMARY, NULL, 0); if (!Success) { - DPRINT("FileFlush failed\n"); + DPRINT1("Failed to flush the primary hive\n"); + return FALSE; } - /* Update second update counter and CheckSum */ + /* + * Increment the secondary sequence number and + * update the checksum. A successful transaction + * writing of hive is both of sequences are the + * same indicating the writing operation didn't + * fail. + */ RegistryHive->BaseBlock->Sequence2++; - RegistryHive->BaseBlock->CheckSum = - HvpHiveHeaderChecksum(RegistryHive->BaseBlock); + RegistryHive->BaseBlock->CheckSum = HvpHiveHeaderChecksum(RegistryHive->BaseBlock); /* Write hive block */ FileOffset = 0; @@ -227,41 +425,113 @@ HvpWriteHive( 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, HFILE_TYPE_PRIMARY, NULL, 0); if (!Success) { - DPRINT("FileFlush failed\n"); + DPRINT1("Failed to flush the primary hive\n"); + return FALSE; } return TRUE; } -BOOLEAN CMAPI -HvSyncHive( - PHHIVE RegistryHive) -{ - ASSERT(RegistryHive->ReadOnly == FALSE); +/* PUBLIC FUNCTIONS ***********************************************************/ - if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~0U) +/** + * @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 == FALSE); + ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); + + /* + * 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; } - /* Update hive header modification time */ - KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp); - - /* Update log file */ - if (!HvpWriteLog(RegistryHive)) + /* + * We are either in Live CD or we are sharing hives. + * In either of the cases, hives can only be read + * so don't do any writing operations on them. + */ +#if !defined(CMLIB_HOST) && !defined(_BLDR_) + if (CmpMiniNTBoot) { - return FALSE; + DPRINT("We are sharing hives or in Live CD mode, abort syncing\n"); + return TRUE; + } +#endif + + /* Avoid any writing operations on volatile hives */ + if (RegistryHive->HiveFlags & HIVE_VOLATILE) + { + DPRINT("The hive is volatile (hive 0x%p)\n", RegistryHive); + return TRUE; } - /* Update hive file */ +#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 log file of hive if present */ + if (RegistryHive->Log == TRUE) + { + 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)) { + DPRINT1("Failed to write the primary hive\n"); +#if !defined(CMLIB_HOST) && !defined(_BLDR_) + IoSetThreadHardErrorMode(HardErrors); +#endif return FALSE; } @@ -269,32 +539,102 @@ HvSyncHive( 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) +HvHiveWillShrink( + _In_ PHHIVE RegistryHive) { /* No shrinking yet */ UNIMPLEMENTED_ONCE; return FALSE; } -BOOLEAN CMAPI +/** + * @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( - PHHIVE RegistryHive) + _In_ PHHIVE RegistryHive) { ASSERT(RegistryHive->ReadOnly == FALSE); + 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)) { + DPRINT1("Failed to write the 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 == FALSE); + ASSERT(RegistryHive->Signature == HV_HHIVE_SIGNATURE); + + /* Call the private API call to do the deed for us */ + return HvpWriteHive(RegistryHive, TRUE); +} + +/* EOF */