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