xref: /reactos/ntoskrnl/config/cmlazy.c (revision 2b7246fd)
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 hive list if this is not a Setup boot */
265     if (!SetupBoot)
266         CmpInitializeHiveList();
267 }
268 
269 NTSTATUS
270 NTAPI
271 CmpCmdHiveOpen(IN POBJECT_ATTRIBUTES FileAttributes,
272                IN PSECURITY_CLIENT_CONTEXT ImpersonationContext,
273                IN OUT PBOOLEAN Allocate,
274                OUT PCMHIVE *NewHive,
275                IN ULONG CheckFlags)
276 {
277     NTSTATUS Status;
278     UNICODE_STRING FileName;
279     PWCHAR FilePath;
280     ULONG Length;
281     OBJECT_NAME_INFORMATION DummyNameInfo;
282     POBJECT_NAME_INFORMATION FileNameInfo;
283 
284     PAGED_CODE();
285 
286     if (FileAttributes->RootDirectory)
287     {
288         /*
289          * Validity check: The ObjectName is relative to RootDirectory,
290          * therefore it must not start with a path separator.
291          */
292         if (FileAttributes->ObjectName && FileAttributes->ObjectName->Buffer &&
293             FileAttributes->ObjectName->Length >= sizeof(WCHAR) &&
294             *FileAttributes->ObjectName->Buffer == OBJ_NAME_PATH_SEPARATOR)
295         {
296             return STATUS_OBJECT_PATH_SYNTAX_BAD;
297         }
298 
299         /* Determine the right buffer size and allocate */
300         Status = ZwQueryObject(FileAttributes->RootDirectory,
301                                ObjectNameInformation,
302                                &DummyNameInfo,
303                                sizeof(DummyNameInfo),
304                                &Length);
305         if (Status != STATUS_BUFFER_OVERFLOW)
306         {
307             DPRINT1("CmpCmdHiveOpen(): Root directory handle object name size query failed, Status = 0x%08lx\n", Status);
308             return Status;
309         }
310 
311         FileNameInfo = ExAllocatePoolWithTag(PagedPool,
312                                              Length + sizeof(UNICODE_NULL),
313                                              TAG_CM);
314         if (FileNameInfo == NULL)
315         {
316             DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
317             return STATUS_INSUFFICIENT_RESOURCES;
318         }
319 
320         /* Try to get the value */
321         Status = ZwQueryObject(FileAttributes->RootDirectory,
322                                ObjectNameInformation,
323                                FileNameInfo,
324                                Length,
325                                &Length);
326         if (!NT_SUCCESS(Status))
327         {
328             /* Fail */
329             DPRINT1("CmpCmdHiveOpen(): Root directory handle object name query failed, Status = 0x%08lx\n", Status);
330             ExFreePoolWithTag(FileNameInfo, TAG_CM);
331             return Status;
332         }
333 
334         /* Null-terminate and add the length of the terminator */
335         Length -= sizeof(OBJECT_NAME_INFORMATION);
336         FilePath = FileNameInfo->Name.Buffer;
337         FilePath[Length / sizeof(WCHAR)] = UNICODE_NULL;
338         Length += sizeof(UNICODE_NULL);
339 
340         /* Compute the size of the full path; Length already counts the terminating NULL */
341         Length = Length + sizeof(WCHAR) + FileAttributes->ObjectName->Length;
342         if (Length > MAXUSHORT)
343         {
344             /* Name size too long, bail out */
345             ExFreePoolWithTag(FileNameInfo, TAG_CM);
346             return STATUS_OBJECT_PATH_INVALID;
347         }
348 
349         /* Build the full path */
350         RtlInitEmptyUnicodeString(&FileName, NULL, 0);
351         FileName.Buffer = ExAllocatePoolWithTag(PagedPool, Length, TAG_CM);
352         if (!FileName.Buffer)
353         {
354             /* Fail */
355             DPRINT1("CmpCmdHiveOpen(): Unable to allocate memory\n");
356             ExFreePoolWithTag(FileNameInfo, TAG_CM);
357             return STATUS_INSUFFICIENT_RESOURCES;
358         }
359         FileName.MaximumLength = Length;
360         RtlCopyUnicodeString(&FileName, &FileNameInfo->Name);
361         ExFreePoolWithTag(FileNameInfo, TAG_CM);
362 
363         /*
364          * Append a path terminator if needed (we have already accounted
365          * for a possible extra one when allocating the buffer).
366          */
367         if (/* FileAttributes->ObjectName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR && */ // We excluded ObjectName starting with a path separator above.
368             FileName.Length > 0 && FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] != OBJ_NAME_PATH_SEPARATOR)
369         {
370             /* ObjectName does not start with '\' and PathBuffer does not end with '\' */
371             FileName.Buffer[FileName.Length / sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR;
372             FileName.Length += sizeof(WCHAR);
373             FileName.Buffer[FileName.Length / sizeof(WCHAR)] = UNICODE_NULL;
374         }
375 
376         /* Append the object name */
377         Status = RtlAppendUnicodeStringToString(&FileName, FileAttributes->ObjectName);
378         if (!NT_SUCCESS(Status))
379         {
380             /* Fail */
381             DPRINT1("CmpCmdHiveOpen(): RtlAppendUnicodeStringToString() failed, Status = 0x%08lx\n", Status);
382             ExFreePoolWithTag(FileName.Buffer, TAG_CM);
383             return Status;
384         }
385     }
386     else
387     {
388         FileName = *FileAttributes->ObjectName;
389     }
390 
391     /* Open the file in the current security context */
392     Status = CmpInitHiveFromFile(&FileName,
393                                  0,
394                                  NewHive,
395                                  Allocate,
396                                  CheckFlags);
397     if (((Status == STATUS_ACCESS_DENIED) ||
398          (Status == STATUS_NO_SUCH_USER) ||
399          (Status == STATUS_WRONG_PASSWORD) ||
400          (Status == STATUS_ACCOUNT_EXPIRED) ||
401          (Status == STATUS_ACCOUNT_DISABLED) ||
402          (Status == STATUS_ACCOUNT_RESTRICTION)) &&
403         (ImpersonationContext))
404     {
405         /* We failed due to an account/security error, impersonate SYSTEM */
406         Status = SeImpersonateClientEx(ImpersonationContext, NULL);
407         if (NT_SUCCESS(Status))
408         {
409             /* Now try again */
410             Status = CmpInitHiveFromFile(&FileName,
411                                          0,
412                                          NewHive,
413                                          Allocate,
414                                          CheckFlags);
415 
416             /* Restore impersonation token */
417             PsRevertToSelf();
418         }
419     }
420 
421     if (FileAttributes->RootDirectory)
422     {
423         ExFreePoolWithTag(FileName.Buffer, TAG_CM);
424     }
425 
426     /* Return status of open attempt */
427     return Status;
428 }
429 
430 VOID
431 NTAPI
432 CmpShutdownWorkers(VOID)
433 {
434     /* Stop lazy flushing */
435     PAGED_CODE();
436     KeCancelTimer(&CmpLazyFlushTimer);
437 }
438 
439 VOID
440 NTAPI
441 CmSetLazyFlushState(IN BOOLEAN Enable)
442 {
443     /* Set state for lazy flusher */
444     CmpHoldLazyFlush = !Enable;
445 }
446 
447 /* EOF */
448