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