/*++ Copyright (c) 1991 Microsoft Corporation Module Name: hivesync.c Abstract: This module implements procedures to write dirty parts of a hive's stable store to backing media. Author: Bryan M. Willman (bryanwi) 28-Mar-92 Environment: Revision History: --*/ #include "cmp.h" extern BOOLEAN HvShutdownComplete; // Set to true after shutdown // to disable any further I/O #if DBG #define DumpDirtyVector(Hive) \ { \ PRTL_BITMAP BitMap; \ ULONG BitMapSize; \ PUCHAR BitBuffer; \ ULONG i; \ UCHAR Byte; \ \ BitMap = &(Hive->DirtyVector); \ BitMapSize = (BitMap->SizeOfBitMap) / 8; \ BitBuffer = (PUCHAR)(BitMap->Buffer); \ for (i = 0; i < BitMapSize; i++) { \ if ((i % 8) == 0) { \ KdPrint(("\n\t")); \ } \ Byte = BitBuffer[i]; \ KdPrint(("%02x ", Byte)); \ } \ KdPrint(("\n")); \ } #else #define DumpDirtyVector(Hive) #endif // // Private prototypes // BOOLEAN HvpWriteLog( PHHIVE Hive ); BOOLEAN HvpFindNextDirtyBlock( PHHIVE Hive, PRTL_BITMAP BitMap, PULONG Current, PUCHAR *Address, PULONG Length, PULONG Offset ); VOID HvpDiscardBins( PHHIVE Hive ); VOID HvpTruncateBins( PHHIVE Hive ); VOID HvRefreshHive( PHHIVE Hive ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,HvMarkCellDirty) #pragma alloc_text(PAGE,HvIsBinDirty) #pragma alloc_text(PAGE,HvMarkDirty) #pragma alloc_text(PAGE,HvMarkClean) #pragma alloc_text(PAGE,HvpGrowLog1) #pragma alloc_text(PAGE,HvpGrowLog2) #pragma alloc_text(PAGE,HvSyncHive) #pragma alloc_text(PAGE,HvpDoWriteHive) #pragma alloc_text(PAGE,HvpWriteLog) #pragma alloc_text(PAGE,HvpFindNextDirtyBlock) #pragma alloc_text(PAGE,HvWriteHive) #pragma alloc_text(PAGE,HvRefreshHive) #pragma alloc_text(PAGE,HvpDiscardBins) #pragma alloc_text(PAGE,HvpTruncateBins) #endif // // Code for tracking modifications and ensuring adequate log space // BOOLEAN HvMarkCellDirty( PHHIVE Hive, HCELL_INDEX Cell ) /*++ Routine Description: Marks the data for the specified cell dirty. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Cell - hcell_index of cell that is being edited Return Value: TRUE - it worked FALSE - could not allocate log space, failure! --*/ { ULONG Type; ULONG Size; PHCELL pCell; PHMAP_ENTRY Me; HCELL_INDEX Base; PHBIN Bin; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvMarkCellDirty:\n\t")); KdPrint(("Hive:%08lx Cell:%08lx\n", Hive, Cell)); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); Type = HvGetCellType(Cell); if ( (Hive->HiveFlags & HIVE_VOLATILE) || (Type == Volatile) ) { return TRUE; } pCell = HvpGetHCell(Hive,Cell); #if DBG Me = HvpGetCellMap(Hive, Cell); Bin = (PHBIN)(Me->BinAddress & HMAP_BASE); ASSERT(Bin->Signature == HBIN_SIGNATURE); #endif // // If it's an old format hive, mark the entire // bin dirty, because the Last backpointers are // such a pain to deal with in the partial // alloc and free-coalescing cases. // if (USE_OLD_CELL(Hive)) { Me = HvpGetCellMap(Hive, Cell); Bin = (PHBIN)(Me->BinAddress & HMAP_BASE); Base = Bin->FileOffset; Size = Bin->Size; return HvMarkDirty(Hive, Base, Size); } else { if (pCell->Size < 0) { Size = -pCell->Size; } else { Size = pCell->Size; } ASSERT(Size < Bin->Size); return HvMarkDirty(Hive, Cell-FIELD_OFFSET(HCELL,u.NewCell), Size); } } BOOLEAN HvIsBinDirty( IN PHHIVE Hive, IN HCELL_INDEX Cell ) /*++ Routine Description: Given a hive and a cell, checks whether the bin containing that cell has any dirty clusters or not. Arguments: Hive - Supplies a pointer to the hive control structure Cell - Supplies the HCELL_INDEX of the Cell. Return Value: TRUE - Bin contains dirty data. FALSE - Bin does not contain dirty data. --*/ { ULONG Type; PHCELL pcell; PRTL_BITMAP Bitmap; ULONG First; ULONG Last; ULONG i; PHMAP_ENTRY Map; PHBIN Bin; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvIsBinDirty:\n\t")); KdPrint(("Hive:%08lx Cell:%08lx\n", Hive, Cell)); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); Type = HvGetCellType(Cell); if ( (Hive->HiveFlags & HIVE_VOLATILE) || (Type == Volatile) ) { return FALSE; } Bitmap = &(Hive->DirtyVector); Map = HvpGetCellMap(Hive, Cell); Bin = (PHBIN)(Map->BinAddress & HMAP_BASE); First = Bin->FileOffset / HSECTOR_SIZE; Last = (Bin->FileOffset + Bin->Size - 1) / HSECTOR_SIZE; for (i=First; i<=Last; i++) { if (RtlCheckBit(Bitmap, i)==1) { return(TRUE); } } return(FALSE); } BOOLEAN HvMarkDirty( PHHIVE Hive, HCELL_INDEX Start, ULONG Length ) /*++ Routine Description: Marks the relevent parts of a hive dirty, so that they will be flushed to backing store. If Hive->Cluster is not 1, then adjacent all logical sectors in the given cluster will be forced dirty (and log space allocated for them.) This must be done here rather than in HvSyncHive so that we can know how much to grow the log. This is a noop for Volatile address range. NOTE: Range will not be marked dirty if operation fails. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Start - supplies a hive virtual address (i.e., an HCELL_INDEX or like form address) of the start of the area to mark dirty. Length - inclusive length in bytes of area to mark dirty. Return Value: TRUE - it worked FALSE - could not allocate log space, failure! --*/ { ULONG Type; PRTL_BITMAP BitMap; ULONG First; ULONG Last; ULONG i; ULONG Cluster; ULONG OriginalDirtyCount; ULONG DirtySectors; BOOLEAN Result = TRUE; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvMarkDirty:\n\t")); KdPrint(("Hive:%08lx Start:%08lx Length:%08lx\n", Hive, Start, Length)); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); Type = HvGetCellType(Start); if ( (Hive->HiveFlags & HIVE_VOLATILE) || (Type == Volatile) ) { return TRUE; } BitMap = &(Hive->DirtyVector); OriginalDirtyCount = Hive->DirtyCount; First = Start / HSECTOR_SIZE; Last = (Start + Length - 1) / HSECTOR_SIZE; Cluster = Hive->Cluster; if (Cluster > 1) { // // Force Start down to base of cluster // Force End up to top of cluster // First = First & ~(Cluster - 1); Last = ROUND_UP(Last+1, Cluster) - 1; } if (Last >= BitMap->SizeOfBitMap) { Last = BitMap->SizeOfBitMap-1; } // // Try and grow the log enough to accomodate all the dirty sectors. // DirtySectors = 0; for (i = First; i <= Last; i++) { if (RtlCheckBit(BitMap, i)==0) { ++DirtySectors; } } // // quick out for common case where it's already dirty. // if (DirtySectors==0) { return(TRUE); } if (HvpGrowLog1(Hive, DirtySectors) == FALSE) { return(FALSE); } if ((OriginalDirtyCount == 0) && (First != 0)) { Result = HvMarkDirty(Hive, 0, sizeof(HBIN)); // force header of 1st bin dirty if (Result==FALSE) { return(FALSE); } } // // Log has been successfully grown, go ahead // and set the dirty bits. // ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); if ((Hive->DirtyCount==0) && (!(Hive->HiveFlags & HIVE_NOLAZYFLUSH))) { CmpLazyFlush(); } Hive->DirtyCount += DirtySectors; RtlSetBits(BitMap, First, Last-First+1); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); return(TRUE); } BOOLEAN HvMarkClean( PHHIVE Hive, HCELL_INDEX Start, ULONG Length ) /*++ Routine Description: Clears the dirty bits for a given portion of a hive. This is the inverse of HvMarkDirty, although it does not give up any file space in the primary or log that HvMarkDirty may have reserved. This is a noop for Volatile address range. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Start - supplies a hive virtual address (i.e., an HCELL_INDEX or like form address) of the start of the area to mark dirty. Length - inclusive length in bytes of area to mark dirty. Return Value: TRUE - it worked --*/ { ULONG Type; PRTL_BITMAP BitMap; ULONG First; ULONG Last; ULONG i; ULONG Cluster; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvMarkClean:\n\t")); KdPrint(("Hive:%08lx Start:%08lx Length:%08lx\n", Hive, Start, Length)); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); Type = HvGetCellType(Start); if ( (Hive->HiveFlags & HIVE_VOLATILE) || (Type == Volatile) ) { return TRUE; } BitMap = &(Hive->DirtyVector); First = Start / HSECTOR_SIZE; Last = (Start + Length - 1) / HSECTOR_SIZE; Cluster = Hive->Cluster; if (Cluster > 1) { // // Force Start down to base of cluster // Force End up to top of cluster // First = First & ~(Cluster - 1); Last = ROUND_UP(Last+1, Cluster) - 1; } if (Last >= BitMap->SizeOfBitMap) { Last = BitMap->SizeOfBitMap-1; } // // Subtract out the dirty count and // and clear the dirty bits. // for (i=First; i<=Last; i++) { if (RtlCheckBit(BitMap,i)==1) { --Hive->DirtyCount; RtlClearBits(BitMap, i, 1); } } ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); return(TRUE); } BOOLEAN HvpGrowLog1( PHHIVE Hive, ULONG Count ) /*++ Routine Description: Adjust the log for growth in the number of sectors of dirty data that are desired. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Count - number of additional logical sectors of log space needed Return Value: TRUE - it worked FALSE - could not allocate log space, failure! --*/ { ULONG ClusterSize; ULONG RequiredSize; ULONG tmp; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvpGrowLog1:\n\t")); KdPrint(("Hive:%08lx Count:%08lx\n", Hive, Count)); } ASSERT(Hive->ReadOnly == FALSE); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); // // If logging is off, tell caller world is OK. // if (Hive->Log == FALSE) { return TRUE; } ClusterSize = Hive->Cluster * HSECTOR_SIZE; tmp = Hive->DirtyVector.SizeOfBitMap / 8; // bytes tmp += sizeof(ULONG); // signature RequiredSize = ClusterSize + // 1 cluster for header ROUND_UP(tmp, ClusterSize) + ((Hive->DirtyCount + Count) * HSECTOR_SIZE); RequiredSize = ROUND_UP(RequiredSize, HLOG_GROW); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); if ( ! (Hive->FileSetSize)(Hive, HFILE_TYPE_LOG, RequiredSize)) { return FALSE; } Hive->LogSize = RequiredSize; ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); return TRUE; } BOOLEAN HvpGrowLog2( PHHIVE Hive, ULONG Size ) /*++ Routine Description: Adjust the log for growth in the size of the hive, in particular, account for the increased space needed for a bigger dirty vector. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Size - proposed growth in size in bytes. Return Value: TRUE - it worked FALSE - could not allocate log space, failure! --*/ { ULONG ClusterSize; ULONG RequiredSize; ULONG DirtyBytes; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvpGrowLog2:\n\t")); KdPrint(("Hive:%08lx Size:%08lx\n", Hive, Size)); } ASSERT(Hive->ReadOnly == FALSE); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); // // If logging is off, tell caller world is OK. // if (Hive->Log == FALSE) { return TRUE; } ASSERT( (Size % HSECTOR_SIZE) == 0 ); ClusterSize = Hive->Cluster * HSECTOR_SIZE; ASSERT( (((Hive->Storage[Stable].Length + Size) / HSECTOR_SIZE) % 8) == 0); DirtyBytes = (Hive->DirtyVector.SizeOfBitMap / 8) + ((Size / HSECTOR_SIZE) / 8) + sizeof(ULONG); // signature DirtyBytes = ROUND_UP(DirtyBytes, ClusterSize); RequiredSize = ClusterSize + // 1 cluster for header (Hive->DirtyCount * HSECTOR_SIZE) + DirtyBytes; RequiredSize = ROUND_UP(RequiredSize, HLOG_GROW); ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); if ( ! (Hive->FileSetSize)(Hive, HFILE_TYPE_LOG, RequiredSize)) { return FALSE; } Hive->LogSize = RequiredSize; ASSERT(Hive->DirtyCount == RtlNumberOfSetBits(&Hive->DirtyVector)); return TRUE; } // // Code for syncing a hive to backing store // BOOLEAN HvSyncHive( PHHIVE Hive ) /*++ Routine Description: Force backing store to match the memory image of the Stable part of the hive's space. Logs, primary, and alternate data can be written. Primary is always written. Normally either a log or an alternate, but not both, will also be written. It is possible to write only the primary. All dirty bits will be set clear. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest Return Value: TRUE - it worked FALSE - some failure. --*/ { CMLOG(CML_WORKER, CMS_IO) { KdPrint(("HvSyncHive:\n\t")); KdPrint(("Hive:%08lx\n", Hive)); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); // // Punt if post shutdown // if (HvShutdownComplete) { CMLOG(CML_BUGCHECK, CMS_IO) { KdPrint(("HvSyncHive: Attempt to sync AFTER SHUTDOWN\n")); } return FALSE; } // // If nothing dirty, do nothing // if (Hive->DirtyCount == 0) { return TRUE; } HvpTruncateBins(Hive); // // If hive is volatile, do nothing // if (Hive->HiveFlags & HIVE_VOLATILE) { return TRUE; } CMLOG(CML_FLOW, CMS_IO) { KdPrint(("\tDirtyCount:%08lx\n", Hive->DirtyCount)); KdPrint(("\tDirtyVector:")); DumpDirtyVector(Hive); } // // Write a log. // if (Hive->Log == TRUE) { if (HvpWriteLog(Hive) == FALSE) { return FALSE; } } // // Write the primary // if (HvpDoWriteHive(Hive, HFILE_TYPE_PRIMARY) == FALSE) { return FALSE; } // // Write an alternate // if (Hive->Alternate == TRUE) { if (HvpDoWriteHive(Hive, HFILE_TYPE_ALTERNATE) == FALSE) { return FALSE; } } // // Hive was successfully written out, discard any bins marked as // discardable. // HvpDiscardBins(Hive); // // Clear the dirty map // RtlClearAllBits(&(Hive->DirtyVector)); Hive->DirtyCount = 0; // // return TRUE; } BOOLEAN HvpDoWriteHive( PHHIVE Hive, ULONG FileType ) /*++ Routine Description: Write dirty parts of the hive out to either its primary or alternate file. Write the header, flush, write all data, flush, update header, flush. Assume either logging or primary/alternate pairs used. NOTE: TimeStamp is not set, assumption is that HvpWriteLog set that. It is only used for checking if Logs correspond anyway. Arguments: Hive - pointer to Hive for which dirty data is to be written. FileType - indicated whether primary or alternate file should be written. Return Value: TRUE - it worked FALSE - it failed --*/ { PHBASE_BLOCK BaseBlock; ULONG Offset; PUCHAR Address; ULONG Length; BOOLEAN rc; ULONG Current; PRTL_BITMAP BitMap; PHMAP_ENTRY Me; PHBIN Bin; BOOLEAN ShrinkHive; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvpDoWriteHive:\n\t")); KdPrint(("Hive:%08lx FileType:%08lx\n", Hive, FileType)); } // // flush first, so that the filesystem structures get written to // disk if we have grown the file. // if (!(Hive->FileFlush)(Hive, FileType)) { return(FALSE); } BaseBlock = Hive->BaseBlock; if (BaseBlock->Length > Hive->Storage[Stable].Length) { ShrinkHive = TRUE; } else { ShrinkHive = FALSE; } // // --- Write out header first time, flush --- // ASSERT(BaseBlock->Signature == HBASE_BLOCK_SIGNATURE); ASSERT(BaseBlock->Major == HSYS_MAJOR); ASSERT(BaseBlock->Format == HBASE_FORMAT_MEMORY); ASSERT(Hive->ReadOnly == FALSE); if (BaseBlock->Sequence1 != BaseBlock->Sequence2) { // // Some previous log attempt failed, or this hive needs to // be recovered, so punt. // return FALSE; } BaseBlock->Length = Hive->Storage[Stable].Length; BaseBlock->Sequence1++; BaseBlock->Type = HFILE_TYPE_PRIMARY; BaseBlock->Cluster = Hive->Cluster; BaseBlock->CheckSum = HvpHeaderCheckSum(BaseBlock); Offset = 0; rc = (Hive->FileWrite)( Hive, FileType, &Offset, (PVOID)BaseBlock, HSECTOR_SIZE * Hive->Cluster ); if (rc == FALSE) { return FALSE; } if ( ! (Hive->FileFlush)(Hive, FileType)) { return FALSE; } Offset = ROUND_UP(Offset, HBLOCK_SIZE); // // --- Write out dirty data (only if there is any) --- // if (Hive->DirtyVector.Buffer != NULL) { // // First sector of first bin will always be dirty, write it out // with the TimeStamp value overlaid on its Link field. // BitMap = &(Hive->DirtyVector); ASSERT(RtlCheckBit(BitMap, 0) == 1); ASSERT(RtlCheckBit(BitMap, (Hive->Cluster - 1)) == 1); ASSERT(sizeof(LIST_ENTRY) >= sizeof(LARGE_INTEGER)); Me = HvpGetCellMap(Hive, 0); Address = (PUCHAR)Me->BlockAddress; Length = Hive->Cluster * HSECTOR_SIZE; Bin = (PHBIN)Address; Bin->TimeStamp = BaseBlock->TimeStamp; rc = (Hive->FileWrite)( Hive, FileType, &Offset, (PVOID)Address, Length ); ASSERT((Offset % (Hive->Cluster * HSECTOR_SIZE)) == 0); if (rc == FALSE) { return FALSE; } // // Write out the rest of the dirty data // Current = Hive->Cluster; // don't rewrite 1st bin or header while (HvpFindNextDirtyBlock( Hive, BitMap, &Current, &Address, &Length, &Offset ) == TRUE) { rc = (Hive->FileWrite)( Hive, FileType, &Offset, (PVOID)Address, Length ); ASSERT((Offset % (Hive->Cluster * HSECTOR_SIZE)) == 0); if (rc == FALSE) { return FALSE; } } } if ( ! (Hive->FileFlush)(Hive, FileType)) { return FALSE; } // // --- Write header again to report completion --- // BaseBlock->Sequence2++; BaseBlock->CheckSum = HvpHeaderCheckSum(BaseBlock); Offset = 0; rc = (Hive->FileWrite)( Hive, FileType, &Offset, BaseBlock, HSECTOR_SIZE * Hive->Cluster ); if (rc == FALSE) { return FALSE; } if (ShrinkHive) { // // Hive has shrunk, give up the excess space. // CmpDoFileSetSize(Hive, FileType, Hive->Storage[Stable].Length + HBLOCK_SIZE); } if ( ! (Hive->FileFlush)(Hive, FileType)) { return FALSE; } if ((Hive->Log) && (Hive->LogSize > HLOG_MINSIZE(Hive))) { // // Shrink log back down, reserve at least two clusters // worth of space so that if all the disk space is // consumed, there will still be enough space prereserved // to allow a minimum of registry operations so the user // can log on. // CmpDoFileSetSize(Hive, HFILE_TYPE_LOG, HLOG_MINSIZE(Hive)); Hive->LogSize = HLOG_MINSIZE(Hive); } return TRUE; } BOOLEAN HvpWriteLog( PHHIVE Hive ) /*++ Routine Description: Write a header, the DirtyVector, and all the dirty data into the log file. Do flushes at the right places. Update the header. Arguments: Hive - pointer to Hive for which dirty data is to be logged. Return Value: TRUE - it worked FALSE - it failed --*/ { PHBASE_BLOCK BaseBlock; ULONG Offset; PUCHAR Address; ULONG Length; BOOLEAN rc; ULONG Current; ULONG junk; ULONG ClusterSize; ULONG HeaderLength; PRTL_BITMAP BitMap; ULONG DirtyVectorSignature = HLOG_DV_SIGNATURE; LARGE_INTEGER systemtime; CMLOG(CML_MINOR, CMS_IO) { KdPrint(("HvpWriteLog:\n\t")); KdPrint(("Hive:%08lx\n", Hive)); } BitMap = &Hive->DirtyVector; // // --- Write out header first time, flush --- // BaseBlock = Hive->BaseBlock; ASSERT(BaseBlock->Signature == HBASE_BLOCK_SIGNATURE); ASSERT(BaseBlock->Major == HSYS_MAJOR); ASSERT(BaseBlock->Format == HBASE_FORMAT_MEMORY); ASSERT(Hive->ReadOnly == FALSE); if (BaseBlock->Sequence1 != BaseBlock->Sequence2) { // // Some previous log attempt failed, or this hive needs to // be recovered, so punt. // return FALSE; } BaseBlock->Sequence1++; KeQuerySystemTime(&systemtime); BaseBlock->TimeStamp = systemtime; BaseBlock->Type = HFILE_TYPE_LOG; ClusterSize = Hive->Cluster * HSECTOR_SIZE; HeaderLength = ROUND_UP(HLOG_HEADER_SIZE, ClusterSize); BaseBlock->Cluster = Hive->Cluster; BaseBlock->CheckSum = HvpHeaderCheckSum(BaseBlock); Offset = 0; rc = (Hive->FileWrite)( Hive, HFILE_TYPE_LOG, &Offset, BaseBlock, HSECTOR_SIZE * Hive->Cluster ); if (rc == FALSE) { return FALSE; } Offset = ROUND_UP(Offset, HeaderLength); if ( ! (Hive->FileFlush)(Hive, HFILE_TYPE_LOG)) { return FALSE; } // // --- Write out dirty vector --- // ASSERT(sizeof(ULONG) == sizeof(DirtyVectorSignature)); // See GrowLog1 above rc = (Hive->FileWrite)( Hive, HFILE_TYPE_LOG, &Offset, (PVOID)&DirtyVectorSignature, sizeof(DirtyVectorSignature) ); if (rc == FALSE) { return FALSE; } Length = Hive->DirtyVector.SizeOfBitMap / 8; Address = (PUCHAR)(Hive->DirtyVector.Buffer); rc = (Hive->FileWrite)( Hive, HFILE_TYPE_LOG, &Offset, (PVOID)Address, Length ); if (rc == FALSE) { return FALSE; } Offset = ROUND_UP(Offset, ClusterSize); // // --- Write out body of log --- // Current = 0; while (HvpFindNextDirtyBlock( Hive, BitMap, &Current, &Address, &Length, &junk ) == TRUE) { rc = (Hive->FileWrite)( Hive, HFILE_TYPE_LOG, &Offset, (PVOID)Address, Length ); ASSERT((Offset % ClusterSize) == 0); if (rc == FALSE) { return FALSE; } } if ( ! (Hive->FileFlush)(Hive, HFILE_TYPE_LOG)) { return FALSE; } // // --- Write header again to report completion --- // BaseBlock->Sequence2++; BaseBlock->CheckSum = HvpHeaderCheckSum(BaseBlock); Offset = 0; rc = (Hive->FileWrite)( Hive, HFILE_TYPE_LOG, &Offset, BaseBlock, HSECTOR_SIZE * Hive->Cluster ); if (rc == FALSE) { return FALSE; } if ( ! (Hive->FileFlush)(Hive, HFILE_TYPE_LOG)) { return FALSE; } return TRUE; } BOOLEAN HvpFindNextDirtyBlock( PHHIVE Hive, PRTL_BITMAP BitMap, PULONG Current, PUCHAR *Address, PULONG Length, PULONG Offset ) /*++ Routine Description: This routine finds and reports the largest run of dirty logical sectors in the hive, which are contiguous in memory and on disk. Arguments: Hive - pointer to Hive of interest. BitMap - supplies a pointer to a bitmap structure, which describes what is dirty. Current - supplies a pointer to a varible that tracks position in the bitmap. It is a bitnumber. It is updated by this call. Address - supplies a pointer to a variable to receive a pointer to the area in memory to be written out. Length - supplies a pointer to a variable to receive the length of the region to read/write Offset - supplies a pointer to a variable to receive the offset in the backing file to which the data should be written. (not valid for log files) Return Value: TRUE - more to write, ret values good FALSE - all data has been written --*/ { ULONG i; ULONG EndOfBitMap; ULONG Start; ULONG End; HCELL_INDEX FileBaseAddress; HCELL_INDEX FileEndAddress; PHMAP_ENTRY Me; PUCHAR Block; PUCHAR StartBlock; PUCHAR NextBlock; ULONG RunSpan; ULONG RunLength; ULONG FileLength; PFREE_HBIN FreeBin; CMLOG(CML_FLOW, CMS_IO) { KdPrint(("HvpFindNextDirtyBlock:\n\t")); KdPrint(("Hive:%08lx Current:%08lx\n", Hive, *Current)); } EndOfBitMap = BitMap->SizeOfBitMap; if (*Current >= EndOfBitMap) { return FALSE; } // // Find next run of set bits // for (i = *Current; i < EndOfBitMap; i++) { if (RtlCheckBit(BitMap, i) == 1) { break; } } Start = i; for ( ; i < EndOfBitMap; i++) { if (RtlCheckBit(BitMap, i) == 0) { break; } } End = i; // // Compute hive virtual addresses, beginning file address, memory address // FileBaseAddress = Start * HSECTOR_SIZE; FileEndAddress = End * HSECTOR_SIZE; FileLength = FileEndAddress - FileBaseAddress; if (FileLength == 0) { *Address = NULL; *Current = 0xffffffff; *Length = 0; return FALSE; } Me = HvpGetCellMap(Hive, FileBaseAddress); if (Me->BinAddress & HMAP_DISCARDABLE) { FreeBin = (PFREE_HBIN)Me->BlockAddress; StartBlock = (PUCHAR)((Me->BinAddress & HMAP_BASE) + FileBaseAddress - FreeBin->FileOffset ); } else { StartBlock = (PUCHAR)Me->BlockAddress; } Block = StartBlock; ASSERT(((PHBIN)(Me->BinAddress & HMAP_BASE))->Signature == HBIN_SIGNATURE); *Address = Block + (FileBaseAddress & HCELL_OFFSET_MASK); *Offset = FileBaseAddress + HBLOCK_SIZE; // // Build up length. First, account for sectors in first block. // RunSpan = HSECTOR_COUNT - (Start % HSECTOR_COUNT); if ((End - Start) <= RunSpan) { // // Entire length is in first block, return it // *Length = FileLength; *Current = End; return TRUE; } else { RunLength = RunSpan * HSECTOR_SIZE; FileBaseAddress = ROUND_UP(FileBaseAddress+1, HBLOCK_SIZE); } // // Scan forward through blocks, filling up length as we go. // // NOTE: This loop grows forward 1 block at time. If we were // really clever we'd fill forward a bin at time, since // bins are always contiguous. But most bins will be // one block long anyway, so we won't bother for now. // while (RunLength < FileLength) { Me = HvpGetCellMap(Hive, FileBaseAddress); ASSERT(((PHBIN)(Me->BinAddress & HMAP_BASE))->Signature == HBIN_SIGNATURE); if (Me->BinAddress & HMAP_DISCARDABLE) { FreeBin = (PFREE_HBIN)Me->BlockAddress; NextBlock = (PUCHAR)((Me->BinAddress & HMAP_BASE) + FileBaseAddress - FreeBin->FileOffset ); } else { NextBlock = (PUCHAR)Me->BlockAddress; } if ( (NextBlock - Block) != HBLOCK_SIZE) { // // We've hit a discontinuity in memory. RunLength is // as long as it's going to get. // break; } if ((FileEndAddress - FileBaseAddress) <= HBLOCK_SIZE) { // // We've reached the tail block, all is contiguous, // fill up to end and return. // *Length = FileLength; *Current = End; return TRUE; } // // Just another contiguous block, fill forward // RunLength += HBLOCK_SIZE; RunSpan += HSECTOR_COUNT; FileBaseAddress += HBLOCK_SIZE; Block = NextBlock; } // // We either hit a discontinuity, OR, we're at the end of the range // we're trying to fill. In either case, return. // *Length = RunLength; *Current = Start + RunSpan; return TRUE; } NTSTATUS HvWriteHive( PHHIVE Hive ) /*++ Routine Description: Write the hive out. Write only to the Primary file, neither logs nor alternates will be updated. The hive will be written to the HFILE_TYPE_EXTERNAL handle. Intended for use in applications like SaveKey. Only Stable storage will be written (as for any hive.) Presumption is that layer above has set HFILE_TYPE_EXTERNAL handle to point to correct place. Applying this call to an active hive will generally hose integrity measures. HOW IT WORKS: Make a new DirtyVector. Fill it with 1s (all dirty). Make hive point at it. We now have what looks like a completely dirty hive. Call HvpWriteHive, which will write the whole thing to disk. Put back DirtyVector, and free the extra one we used. In failure case, force Sequence numbers in Hive and BaseBlock to match. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest. Return Value: Status. --*/ { PULONG SaveDirtyVector; ULONG SaveDirtyVectorSize; PULONG AltDirtyVector; ULONG AltDirtyVectorSize; PHBASE_BLOCK SaveBaseBlock; PHBASE_BLOCK AltBaseBlock; ULONG Alignment; NTSTATUS status; CMLOG(CML_MAJOR, CMS_IO) { KdPrint(("HvWriteHive: \n")); KdPrint(("\tHive = %08lx\n")); } ASSERT(Hive->Signature == HHIVE_SIGNATURE); ASSERT(Hive->ReadOnly == FALSE); // // Punt if post shutdown // if (HvShutdownComplete) { CMLOG(CML_BUGCHECK, CMS_IO) { KdPrint(("HvWriteHive: Attempt to write hive AFTER SHUTDOWN\n")); } return STATUS_REGISTRY_IO_FAILED; } // // Splice in a duplicate DirtyVector with all bits set. // SaveDirtyVector = Hive->DirtyVector.Buffer; SaveDirtyVectorSize = Hive->DirtyVector.SizeOfBitMap; SaveBaseBlock = Hive->BaseBlock; AltDirtyVectorSize = (Hive->Storage[Stable].Length / HSECTOR_SIZE) / 8; AltDirtyVector = (Hive->Allocate)(ROUND_UP(AltDirtyVectorSize,sizeof(ULONG)), FALSE); if (AltDirtyVector == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit1; } Hive->DirtyVector.Buffer = AltDirtyVector; Hive->DirtyVector.SizeOfBitMap = AltDirtyVectorSize * 8; RtlSetAllBits(&(Hive->DirtyVector)); // // Splice in a duplicate BaseBlock // AltBaseBlock = (Hive->Allocate)(sizeof(HBASE_BLOCK), TRUE); if (AltBaseBlock == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit2; } // // Make sure the buffer we got back is cluster-aligned. If not, try // harder to get an aligned buffer. // Alignment = Hive->Cluster * HSECTOR_SIZE - 1; if (((ULONG)AltBaseBlock & Alignment) != 0) { (Hive->Free)(AltBaseBlock, sizeof(HBASE_BLOCK)); AltBaseBlock = (PHBASE_BLOCK)((Hive->Allocate)(PAGE_SIZE, TRUE)); if (AltBaseBlock == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Exit2; } } RtlMoveMemory(AltBaseBlock, SaveBaseBlock, HSECTOR_SIZE); Hive->BaseBlock = AltBaseBlock; // // Ensure the file can be made big enough, then do the deed // status = CmpDoFileSetSize(Hive, HFILE_TYPE_EXTERNAL, Hive->Storage[Stable].Length); if (NT_SUCCESS(status)) { if (!HvpDoWriteHive(Hive, HFILE_TYPE_EXTERNAL)) { status = STATUS_REGISTRY_IO_FAILED; } } // // Clean up, success or failure // CmpFree(AltBaseBlock, sizeof(HBASE_BLOCK)); Exit2: CmpFree(AltDirtyVector, AltDirtyVectorSize); Exit1: Hive->DirtyVector.Buffer = SaveDirtyVector; Hive->DirtyVector.SizeOfBitMap = SaveDirtyVectorSize; Hive->BaseBlock = SaveBaseBlock; return status; } VOID HvRefreshHive( PHHIVE Hive ) /*++ Routine Description: Undo the last sync. We do this by reading back all data which has been marked dirty from the file into memory. Update the free list. We will then clear the dirty vector. Any growth since the last sync will be discarded, and in fact, the size of the file will be set down. WARNNOTE: Failure will cause a bugcheck, as we cannot keep the hive consistent if we fail part way through. All I/O is done via HFILE_TYPE_PRIMARY. Arguments: Hive - supplies a pointer to the hive control structure for the hive of interest. Return Value: NONE. Either works or BugChecks. --*/ { ULONG Offset; ULONG ReadLength; ULONG checkstatus; PUCHAR Address; ULONG Current; PRTL_BITMAP BitMap; BOOLEAN rc; ULONG Start; ULONG End; ULONG BitLength; HCELL_INDEX TailStart; HCELL_INDEX p; PHMAP_ENTRY t; ULONG i; PHBIN Bin; PLIST_ENTRY List; PFREE_HBIN FreeBin; PHMAP_ENTRY Map; // // noop or assert on various uninteresting or bogus conditions // if (Hive->DirtyCount == 0) { return; } ASSERT(Hive->HiveFlags & HIVE_NOLAZYFLUSH); ASSERT(Hive->Storage[Volatile].Length == 0); // // be sure the hive is not already trash // checkstatus = HvCheckHive(Hive, NULL); if (checkstatus != 0) { KeBugCheckEx(REGISTRY_ERROR,7,0,(ULONG)Hive,checkstatus); } Hive->RefreshCount++; // // Any bins that have been marked as discardable, but not yet flushed to // disk, are going to be overwritten with old data. Bring them back into // memory and remove their FREE_HBIN marker from the list. // List = Hive->Storage[Stable].FreeBins.Flink; while (List != &Hive->Storage[Stable].FreeBins) { FreeBin = CONTAINING_RECORD(List, FREE_HBIN, ListEntry); List = List->Flink; if (FreeBin->Flags & FREE_HBIN_DISCARDABLE) { for (i=0; iSize; i+=HBLOCK_SIZE) { Map = HvpGetCellMap(Hive, FreeBin->FileOffset+i); Map->BlockAddress = (Map->BinAddress & HMAP_BASE)+i; Map->BinAddress &= ~HMAP_DISCARDABLE; } RemoveEntryList(&FreeBin->ListEntry); (Hive->Free)(FreeBin, sizeof(FREE_HBIN)); } } // // OverRead base block. // Offset = 0; if ( (Hive->FileRead)( Hive, HFILE_TYPE_PRIMARY, &Offset, Hive->BaseBlock, HBLOCK_SIZE ) != TRUE) { KeBugCheckEx(REGISTRY_ERROR,7,1,0,0); } TailStart = (HCELL_INDEX)(Hive->BaseBlock->Length); // // Free "tail" memory and maps for it, update hive size pointers // HvFreeHivePartial(Hive, TailStart, Stable); // // Clear dirty vector for data past Hive->BaseBlock->Length // Start = Hive->BaseBlock->Length / HSECTOR_SIZE; End = Hive->DirtyVector.SizeOfBitMap; BitLength = End - Start; RtlClearBits(&(Hive->DirtyVector), Start, BitLength); // // Scan dirty blocks. Read contiguous blocks off disk into hive. // Stop when we get to reduced length. // BitMap = &(Hive->DirtyVector); Current = 0; while (HvpFindNextDirtyBlock( Hive, &Hive->DirtyVector, &Current, &Address, &ReadLength, &Offset )) { ASSERT(Offset < (Hive->BaseBlock->Length + sizeof(HBASE_BLOCK))); rc = (Hive->FileRead)( Hive, HFILE_TYPE_PRIMARY, &Offset, (PVOID)Address, ReadLength ); if (rc == FALSE) { KeBugCheckEx(REGISTRY_ERROR,7,2,(ULONG)&Offset,rc); } } // // If we read the start of any HBINs into memory, it is likely // their MemAlloc fields are invalid. Walk through the HBINs // and write valid MemAlloc values for any HBINs whose first // sector was reread. // p=0; while (p < Hive->Storage[Stable].Length) { t = HvpGetCellMap(Hive, p); ASSERT(t != NULL); Bin = (PHBIN)(t->BlockAddress & HMAP_BASE); if ((t->BinAddress & HMAP_DISCARDABLE)==0) { if (RtlCheckBit(&Hive->DirtyVector, p / HSECTOR_SIZE)==1) { // // The first sector in the HBIN is dirty. // // Reset the BinAddress to the Block address to cover // the case where a few smaller bins have been coalesced // into a larger bin. We want the smaller bins back now. // t->BinAddress = (t->BinAddress & ~HMAP_BASE) | t->BlockAddress; // Check the map to see if this is the start // of a memory allocation or not. // if (t->BinAddress & HMAP_NEWALLOC) { // // Walk through the map to determine the length // of the allocation. // Bin->MemAlloc = 0; do { t = HvpGetCellMap(Hive, p+Bin->MemAlloc+HBLOCK_SIZE); Bin->MemAlloc += HBLOCK_SIZE; if (p+Bin->MemAlloc == Hive->Storage[Stable].Length) { // // Reached the end of the hive. // break; } } while ( (t->BinAddress & HMAP_NEWALLOC) == 0); } else { Bin->MemAlloc = 0; } } p += Bin->Size; } else { FreeBin = (PFREE_HBIN)t->BlockAddress; p += FreeBin->Size; } } // // be sure we haven't filled memory with trash // checkstatus = HvCheckHive(Hive, NULL); if (checkstatus != 0) { KeBugCheckEx(REGISTRY_ERROR,7,3,(ULONG)Hive,checkstatus); } // // reinit the free list // for (i = 0; i < HHIVE_FREE_DISPLAY_SIZE; i++) { Hive->Storage[Stable].FreeDisplay[i] = HCELL_NIL; } Hive->Storage[Stable].FreeSummary = 0; // // rebuild the free list // p = 0; while (p < Hive->Storage[Stable].Length) { t = HvpGetCellMap(Hive, p); if (t == NULL) { KeBugCheckEx(REGISTRY_ERROR,7,4,(ULONG)Hive,p); } if ((t->BinAddress & HMAP_DISCARDABLE) == 0) { Bin = (PHBIN)((t->BinAddress) & HMAP_BASE); if ( ! HvpEnlistFreeCells(Hive, Bin, Bin->FileOffset)) { KeBugCheckEx(REGISTRY_ERROR,7,5,(ULONG)Bin,Bin->FileOffset); } p = (ULONG)p + Bin->Size; } else { FreeBin = (PFREE_HBIN)t->BlockAddress; p = (ULONG)p + FreeBin->Size; } } // // be sure the structure of the thing is OK after all this // checkstatus = CmCheckRegistry((PCMHIVE)Hive, FALSE); if (checkstatus != 0) { KeBugCheckEx(REGISTRY_ERROR,7,6,(ULONG)Hive,checkstatus); } // // Clear dirty vector. // RtlClearAllBits(&(Hive->DirtyVector)); Hive->DirtyCount = 0; // // Adjust the file size, if this fails, ignore it, since it just // means the file is too big. // (Hive->FileSetSize)( Hive, HFILE_TYPE_PRIMARY, (Hive->BaseBlock->Length + HBLOCK_SIZE) ); return; } VOID HvpDiscardBins( IN PHHIVE Hive ) /*++ Routine Description: Walks through the dirty bins in a hive to see if any are marked discardable. If so, they are discarded and the map is updated to reflect this. Arguments: Hive - Supplies the hive control structure. Return Value: None. --*/ { PHBIN Bin; PHMAP_ENTRY Map; PHMAP_ENTRY PreviousMap; PHMAP_ENTRY NextMap; PFREE_HBIN FreeBin; PFREE_HBIN PreviousFreeBin; PFREE_HBIN NextFreeBin; PLIST_ENTRY List; List = Hive->Storage[Stable].FreeBins.Flink; while (List != &Hive->Storage[Stable].FreeBins) { ASSERT_LISTENTRY(List); FreeBin = CONTAINING_RECORD(List, FREE_HBIN, ListEntry); if (FreeBin->Flags & FREE_HBIN_DISCARDABLE) { Map = HvpGetCellMap(Hive, FreeBin->FileOffset); Bin = (PHBIN)(Map->BinAddress & HMAP_BASE); ASSERT(Map->BinAddress & HMAP_DISCARDABLE); // // Note we use ExFreePool directly here to avoid // giving back the quota for this bin. By charging // registry quota for discarded bins, we prevent // sparse hives from requiring more quota after // a reboot than on a running system. // ExFreePool(Bin); FreeBin->Flags &= ~FREE_HBIN_DISCARDABLE; } List=List->Flink; } } VOID HvpTruncateBins( IN PHHIVE Hive ) /*++ Routine Description: Attempts to shrink the hive by truncating any bins that are discardable at the end of the hive. Applies to both stable and volatile storage. Arguments: Hive - Supplies the hive to be truncated. Return Value: None. --*/ { HSTORAGE_TYPE i; PHMAP_ENTRY Map; ULONG NewLength; PFREE_HBIN FreeBin; // // stable and volatile // for (i=0;iStorage[i].Length; while (NewLength > 0) { Map = HvpGetCellMap(Hive, (NewLength - HBLOCK_SIZE) + (i*HCELL_TYPE_MASK)); if (Map->BinAddress & HMAP_DISCARDABLE) { FreeBin = (PFREE_HBIN)Map->BlockAddress; NewLength = FreeBin->FileOffset; } else { break; } } if (NewLength < Hive->Storage[i].Length) { // // There are some free bins to truncate. // HvFreeHivePartial(Hive, NewLength, i); } } }