xref: /reactos/ntoskrnl/config/cmlazy.c (revision 0b366ea1)
1 /*
2  * PROJECT:     ReactOS Kernel
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * PURPOSE:     Configuration Manager - Internal Registry APIs
5  * PROGRAMMERS: Alex Ionescu <alex.ionescu@reactos.org>
6  */
7 
8 /* INCLUDES *******************************************************************/
9 
10 #include "ntoskrnl.h"
11 #define NDEBUG
12 #include "debug.h"
13 
14 /* GLOBALS ********************************************************************/
15 
16 KTIMER CmpLazyFlushTimer;
17 KDPC CmpLazyFlushDpc;
18 WORK_QUEUE_ITEM CmpLazyWorkItem;
19 KTIMER CmpEnableLazyFlushTimer;
20 KDPC CmpEnableLazyFlushDpc;
21 BOOLEAN CmpLazyFlushPending;
22 BOOLEAN CmpForceForceFlush;
23 BOOLEAN CmpHoldLazyFlush = TRUE;
24 ULONG CmpLazyFlushIntervalInSeconds = 5;
25 static ULONG CmpLazyFlushHiveCount = 7;
26 ULONG CmpLazyFlushCount = 1;
27 LONG CmpFlushStarveWriters;
28 
29 /* FUNCTIONS ******************************************************************/
30 
31 BOOLEAN
32 NTAPI
33 CmpDoFlushNextHive(_In_  BOOLEAN ForceFlush,
34                    _Out_ PBOOLEAN Error,
35                    _Out_ PULONG DirtyCount)
36 {
37     NTSTATUS Status;
38     PLIST_ENTRY NextEntry;
39     PCMHIVE CmHive;
40     BOOLEAN Result;
41     ULONG HiveCount = CmpLazyFlushHiveCount;
42 
43     /* Set Defaults */
44     *Error = FALSE;
45     *DirtyCount = 0;
46 
47     /* Don't do anything if we're not supposed to */
48     if (CmpNoWrite) return TRUE;
49 
50     /* Make sure we have to flush at least one hive */
51     if (!HiveCount) HiveCount = 1;
52 
53     /* Acquire the list lock and loop */
54     ExAcquirePushLockShared(&CmpHiveListHeadLock);
55     NextEntry = CmpHiveListHead.Flink;
56     while ((NextEntry != &CmpHiveListHead) && HiveCount)
57     {
58         /* Get the hive and check if we should flush it */
59         CmHive = CONTAINING_RECORD(NextEntry, CMHIVE, HiveList);
60         if (!(CmHive->Hive.HiveFlags & HIVE_NOLAZYFLUSH) &&
61             (CmHive->FlushCount != CmpLazyFlushCount))
62         {
63             /* Great sucess! */
64             Result = TRUE;
65 
66             /* One less to flush */
67             HiveCount--;
68 
69             /* Ignore clean or volatile hives */
70             if ((!CmHive->Hive.DirtyCount && !ForceFlush) ||
71                 (CmHive->Hive.HiveFlags & HIVE_VOLATILE))
72             {
73                 /* Don't do anything but do update the count */
74                 CmHive->FlushCount = CmpLazyFlushCount;
75                 DPRINT("Hive %wZ is clean.\n", &CmHive->FileFullPath);
76             }
77             else
78             {
79                 /* Do the sync */
80                 DPRINT("Flushing: %wZ\n", &CmHive->FileFullPath);
81                 DPRINT("Handle: %p\n", CmHive->FileHandles[HFILE_TYPE_PRIMARY]);
82                 Status = HvSyncHive(&CmHive->Hive);
83                 if(!NT_SUCCESS(Status))
84                 {
85                     /* Let them know we failed */
86                     DPRINT1("Failed to flush %wZ on handle %p (status 0x%08lx)\n",
87                         &CmHive->FileFullPath,  CmHive->FileHandles[HFILE_TYPE_PRIMARY], Status);
88                     *Error = TRUE;
89                     Result = FALSE;
90                     break;
91                 }
92                 CmHive->FlushCount = CmpLazyFlushCount;
93             }
94         }
95         else if (CmHive->Hive.DirtyCount &&
96                  !(CmHive->Hive.HiveFlags & HIVE_VOLATILE) &&
97                  !(CmHive->Hive.HiveFlags & HIVE_NOLAZYFLUSH))
98         {
99             /* Use another lazy flusher for this hive */
100             ASSERT(CmHive->FlushCount == CmpLazyFlushCount);
101             *DirtyCount += CmHive->Hive.DirtyCount;
102             DPRINT("CmHive %wZ already uptodate.\n", &CmHive->FileFullPath);
103         }
104 
105         /* Try the next one */
106         NextEntry = NextEntry->Flink;
107     }
108 
109     /* Check if we've flushed everything */
110     if (NextEntry == &CmpHiveListHead)
111     {
112         /* We have, tell the caller we're done */
113         Result = FALSE;
114     }
115     else
116     {
117         /* We need to be called again */
118         Result = TRUE;
119     }
120 
121     /* Unlock the list and return the result */
122     ExReleasePushLock(&CmpHiveListHeadLock);
123     return Result;
124 }
125 
126 _Function_class_(KDEFERRED_ROUTINE)
127 VOID
128 NTAPI
129 CmpEnableLazyFlushDpcRoutine(IN PKDPC Dpc,
130                              IN PVOID DeferredContext,
131                              IN PVOID SystemArgument1,
132                              IN PVOID SystemArgument2)
133 {
134     /* Don't stop lazy flushing from happening anymore */
135     CmpHoldLazyFlush = FALSE;
136 }
137 
138 _Function_class_(KDEFERRED_ROUTINE)
139 VOID
140 NTAPI
141 CmpLazyFlushDpcRoutine(IN PKDPC Dpc,
142                        IN PVOID DeferredContext,
143                        IN PVOID SystemArgument1,
144                        IN PVOID SystemArgument2)
145 {
146     /* Check if we should queue the lazy flush worker */
147     DPRINT("Flush pending: %s, Holding lazy flush: %s.\n", CmpLazyFlushPending ? "yes" : "no", CmpHoldLazyFlush ? "yes" : "no");
148     if (!CmpLazyFlushPending && !CmpHoldLazyFlush)
149     {
150         CmpLazyFlushPending = TRUE;
151         ExQueueWorkItem(&CmpLazyWorkItem, DelayedWorkQueue);
152     }
153 }
154 
155 VOID
156 NTAPI
157 CmpLazyFlush(VOID)
158 {
159     LARGE_INTEGER DueTime;
160     PAGED_CODE();
161 
162     /* Check if we should set the lazy flush timer */
163     if (!CmpNoWrite && !CmpHoldLazyFlush)
164     {
165         /* Do it */
166         DueTime.QuadPart = Int32x32To64(CmpLazyFlushIntervalInSeconds,
167                                         -10 * 1000 * 1000);
168         KeSetTimer(&CmpLazyFlushTimer, DueTime, &CmpLazyFlushDpc);
169     }
170 }
171 
172 _Function_class_(WORKER_THREAD_ROUTINE)
173 VOID
174 NTAPI
175 CmpLazyFlushWorker(IN PVOID Parameter)
176 {
177     BOOLEAN ForceFlush, Result, MoreWork = FALSE;
178     ULONG DirtyCount = 0;
179     PAGED_CODE();
180 
181     /* Don't do anything if lazy flushing isn't enabled yet */
182     if (CmpHoldLazyFlush)
183     {
184         DPRINT1("Lazy flush held. Bye bye.\n");
185         CmpLazyFlushPending = FALSE;
186         return;
187     }
188 
189     /* Check if we are forcing a flush */
190     ForceFlush = CmpForceForceFlush;
191     if (ForceFlush)
192     {
193         DPRINT("Forcing flush.\n");
194         /* Lock the registry exclusively */
195         CmpLockRegistryExclusive();
196     }
197     else
198     {
199         DPRINT("Not forcing flush.\n");
200         /* Starve writers before locking */
201         InterlockedIncrement(&CmpFlushStarveWriters);
202         CmpLockRegistry();
203     }
204 
205     /* Flush the next hive */
206     MoreWork = CmpDoFlushNextHive(ForceFlush, &Result, &DirtyCount);
207     if (!MoreWork)
208     {
209         /* We're done */
210         InterlockedIncrement((PLONG)&CmpLazyFlushCount);
211     }
212 
213     /* Check if we have starved writers */
214     if (!ForceFlush)
215         InterlockedDecrement(&CmpFlushStarveWriters);
216 
217     /* Not pending anymore, release the registry lock */
218     CmpLazyFlushPending = FALSE;
219     CmpUnlockRegistry();
220 
221     DPRINT("Lazy flush done. More work to be done: %s. Entries still dirty: %u.\n",
222         MoreWork ? "Yes" : "No", DirtyCount);
223 
224     if (MoreWork)
225     {
226         /* Relaunch the flush timer, so the remaining hives get flushed */
227         CmpLazyFlush();
228     }
229 }
230 
231 VOID
232 NTAPI
233 CmpCmdInit(IN BOOLEAN SetupBoot)
234 {
235     LARGE_INTEGER DueTime;
236     PAGED_CODE();
237 
238     /* Setup the lazy DPC */
239     KeInitializeDpc(&CmpLazyFlushDpc, CmpLazyFlushDpcRoutine, NULL);
240 
241     /* Setup the lazy timer */
242     KeInitializeTimer(&CmpLazyFlushTimer);
243 
244     /* Setup the lazy worker */
245     ExInitializeWorkItem(&CmpLazyWorkItem, CmpLazyFlushWorker, NULL);
246 
247     /* Setup the forced-lazy DPC and timer */
248     KeInitializeDpc(&CmpEnableLazyFlushDpc,
249                     CmpEnableLazyFlushDpcRoutine,
250                     NULL);
251     KeInitializeTimer(&CmpEnableLazyFlushTimer);
252 
253     /* Enable lazy flushing after 10 minutes */
254     DueTime.QuadPart = Int32x32To64(600, -10 * 1000 * 1000);
255     KeSetTimer(&CmpEnableLazyFlushTimer, DueTime, &CmpEnableLazyFlushDpc);
256 
257     /* Setup flush variables */
258     CmpNoWrite = CmpMiniNTBoot;
259     CmpWasSetupBoot = SetupBoot;
260 
261     /* Testing: Force Lazy Flushing */
262     CmpHoldLazyFlush = FALSE;
263 
264     /* Setup the system hives list if this is not a Setup boot */
265     if (!SetupBoot)
266         CmpInitializeHiveList();
267 
268     /* Now that the system hives are loaded, if we are in PE mode,
269      * all other hives will be loaded with full access */
270     if (CmpMiniNTBoot)
271         CmpShareSystemHives = FALSE;
272 }
273 
274 NTSTATUS
275 NTAPI
276 CmpCmdHiveOpen(IN POBJECT_ATTRIBUTES FileAttributes,
277                IN PSECURITY_CLIENT_CONTEXT ImpersonationContext,
278                IN OUT PBOOLEAN Allocate,
279                OUT PCMHIVE *NewHive,
280                IN ULONG CheckFlags)
281 {
282     NTSTATUS Status;
283     UNICODE_STRING FileName;
284     PWCHAR FilePath;
285     ULONG Length;
286     OBJECT_NAME_INFORMATION DummyNameInfo;
287     POBJECT_NAME_INFORMATION FileNameInfo;
288 
289     PAGED_CODE();
290 
291     if (FileAttributes->RootDirectory)
292     {
293         /*
294          * Validity check: The ObjectName is relative to RootDirectory,
295          * therefore it must not start with a path separator.
296          */
297         if (FileAttributes->ObjectName && FileAttributes->ObjectName->Buffer &&
298             FileAttributes->ObjectName->Length >= sizeof(WCHAR) &&
299             *FileAttributes->ObjectName->Buffer == OBJ_NAME_PATH_SEPARATOR)
300         {
301             return STATUS_OBJECT_PATH_SYNTAX_BAD;
302         }
303 
304         /* Determine the right buffer size and allocate */
305         Status = ZwQueryObject(FileAttributes->RootDirectory,
306                                ObjectNameInformation,
307                                &DummyNameInfo,
308                                sizeof(DummyNameInfo),
309                                &Length);
310         if (Status != STATUS_BUFFER_OVERFLOW)
311         {
312             DPRINT1("CmpCmdHiveOpen(): Root directory handle object name size query failed, Status = 0x%08lx\n", Status);
313             return Status;
314         }
315 
316         FileNameInfo = ExAllocatePoolWithTag(PagedPool,
317                                              Length + sizeof(UNICODE_NULL),
318                                              TAG_CM);
319         if (FileNameInfo == NULL)
320         {
321             DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
322             return STATUS_INSUFFICIENT_RESOURCES;
323         }
324 
325         /* Try to get the value */
326         Status = ZwQueryObject(FileAttributes->RootDirectory,
327                                ObjectNameInformation,
328                                FileNameInfo,
329                                Length,
330                                &Length);
331         if (!NT_SUCCESS(Status))
332         {
333             /* Fail */
334             DPRINT1("CmpCmdHiveOpen(): Root directory handle object name query failed, Status = 0x%08lx\n", Status);
335             ExFreePoolWithTag(FileNameInfo, TAG_CM);
336             return Status;
337         }
338 
339         /* Null-terminate and add the length of the terminator */
340         Length -= sizeof(OBJECT_NAME_INFORMATION);
341         FilePath = FileNameInfo->Name.Buffer;
342         FilePath[Length / sizeof(WCHAR)] = UNICODE_NULL;
343         Length += sizeof(UNICODE_NULL);
344 
345         /* Compute the size of the full path; Length already counts the terminating NULL */
346         Length = Length + sizeof(WCHAR) + FileAttributes->ObjectName->Length;
347         if (Length > MAXUSHORT)
348         {
349             /* Name size too long, bail out */
350             ExFreePoolWithTag(FileNameInfo, TAG_CM);
351             return STATUS_OBJECT_PATH_INVALID;
352         }
353 
354         /* Build the full path */
355         RtlInitEmptyUnicodeString(&FileName, NULL, 0);
356         FileName.Buffer = ExAllocatePoolWithTag(PagedPool, Length, TAG_CM);
357         if (!FileName.Buffer)
358         {
359             /* Fail */
360             DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
361             ExFreePoolWithTag(FileNameInfo, TAG_CM);
362             return STATUS_INSUFFICIENT_RESOURCES;
363         }
364         FileName.MaximumLength = Length;
365         RtlCopyUnicodeString(&FileName, &FileNameInfo->Name);
366         ExFreePoolWithTag(FileNameInfo, TAG_CM);
367 
368         /*
369          * Append a path terminator if needed (we have already accounted
370          * for a possible extra one when allocating the buffer).
371          */
372         if (/* FileAttributes->ObjectName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR && */ // We excluded ObjectName starting with a path separator above.
373             FileName.Length > 0 && FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] != OBJ_NAME_PATH_SEPARATOR)
374         {
375             /* ObjectName does not start with '\' and PathBuffer does not end with '\' */
376             FileName.Buffer[FileName.Length / sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR;
377             FileName.Length += sizeof(WCHAR);
378             FileName.Buffer[FileName.Length / sizeof(WCHAR)] = UNICODE_NULL;
379         }
380 
381         /* Append the object name */
382         Status = RtlAppendUnicodeStringToString(&FileName, FileAttributes->ObjectName);
383         if (!NT_SUCCESS(Status))
384         {
385             /* Fail */
386             DPRINT1("CmpCmdHiveOpen(): RtlAppendUnicodeStringToString() failed, Status = 0x%08lx\n", Status);
387             ExFreePoolWithTag(FileName.Buffer, TAG_CM);
388             return Status;
389         }
390     }
391     else
392     {
393         FileName = *FileAttributes->ObjectName;
394     }
395 
396     /* Open the file in the current security context */
397     Status = CmpInitHiveFromFile(&FileName,
398                                  0,
399                                  NewHive,
400                                  Allocate,
401                                  CheckFlags);
402     if (((Status == STATUS_ACCESS_DENIED) ||
403          (Status == STATUS_NO_SUCH_USER) ||
404          (Status == STATUS_WRONG_PASSWORD) ||
405          (Status == STATUS_ACCOUNT_EXPIRED) ||
406          (Status == STATUS_ACCOUNT_DISABLED) ||
407          (Status == STATUS_ACCOUNT_RESTRICTION)) &&
408         ImpersonationContext)
409     {
410         /* We failed due to an account/security error, impersonate SYSTEM */
411         Status = SeImpersonateClientEx(ImpersonationContext, NULL);
412         if (NT_SUCCESS(Status))
413         {
414             /* Now try again */
415             Status = CmpInitHiveFromFile(&FileName,
416                                          0,
417                                          NewHive,
418                                          Allocate,
419                                          CheckFlags);
420 
421             /* Restore impersonation token */
422             PsRevertToSelf();
423         }
424     }
425 
426     if (FileAttributes->RootDirectory)
427     {
428         ExFreePoolWithTag(FileName.Buffer, TAG_CM);
429     }
430 
431     /* Return status of open attempt */
432     return Status;
433 }
434 
435 VOID
436 NTAPI
437 CmpShutdownWorkers(VOID)
438 {
439     /* Stop lazy flushing */
440     PAGED_CODE();
441     KeCancelTimer(&CmpLazyFlushTimer);
442 }
443 
444 VOID
445 NTAPI
446 CmSetLazyFlushState(IN BOOLEAN Enable)
447 {
448     /* Set state for lazy flusher */
449     CmpHoldLazyFlush = !Enable;
450 }
451 
452 /* EOF */
453