[NTOSKRNL]
[reactos.git] / reactos / ntoskrnl / ps / quota.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ps/quota.c
5 * PURPOSE: Process Pool Quotas
6 *
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
8 * Mike Nordell
9 */
10
11 /* INCLUDES **************************************************************/
12
13 #include <ntoskrnl.h>
14 #include <ntintsafe.h>
15 #define NDEBUG
16 #include <debug.h>
17
18 EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock;
19 static LIST_ENTRY PspQuotaBlockList = {&PspQuotaBlockList, &PspQuotaBlockList};
20 static KSPIN_LOCK PspQuotaLock;
21
22 #define TAG_QUOTA_BLOCK 'bQsP'
23 #define VALID_QUOTA_FLAGS (QUOTA_LIMITS_HARDWS_MIN_ENABLE | \
24 QUOTA_LIMITS_HARDWS_MIN_DISABLE | \
25 QUOTA_LIMITS_HARDWS_MAX_ENABLE | \
26 QUOTA_LIMITS_HARDWS_MAX_DISABLE)
27
28 /* PRIVATE FUNCTIONS *******************************************************/
29
30 /*
31 * Private helper to charge the specified process quota.
32 * ReturnsSTATUS_QUOTA_EXCEEDED on quota limit check failure.
33 * Updates QuotaPeak as needed for specified PoolIndex.
34 * TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
35 * to replace UCHAR for 'PoolIndex'.
36 * Notes: Conceptually translation unit local/private.
37 */
38 NTSTATUS
39 NTAPI
40 PspChargeProcessQuotaSpecifiedPool(IN PEPROCESS Process,
41 IN UCHAR PoolIndex,
42 IN SIZE_T Amount)
43 {
44 ASSERT(Process);
45 ASSERT(Process != PsInitialSystemProcess);
46 ASSERT(PoolIndex <= 2);
47 ASSERT(Process->QuotaBlock);
48
49 /* Note: Race warning. TODO: Needs to add/use lock for this */
50 if (Process->QuotaUsage[PoolIndex] + Amount >
51 Process->QuotaBlock->QuotaEntry[PoolIndex].Limit)
52 {
53 DPRINT1("Quota exceeded, but ROS will let it slide...\n");
54 return STATUS_SUCCESS;
55 //return STATUS_QUOTA_EXCEEDED; /* caller raises the exception */
56 }
57
58 InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex], Amount);
59
60 /* Note: Race warning. TODO: Needs to add/use lock for this */
61 if (Process->QuotaPeak[PoolIndex] < Process->QuotaUsage[PoolIndex])
62 {
63 Process->QuotaPeak[PoolIndex] = Process->QuotaUsage[PoolIndex];
64 }
65
66 return STATUS_SUCCESS;
67 }
68
69 /*
70 * Private helper to remove quota charge from the specified process quota.
71 * TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
72 * to replace UCHAR for 'PoolIndex'.
73 * Notes: Conceptually translation unit local/private.
74 */
75 VOID
76 NTAPI
77 PspReturnProcessQuotaSpecifiedPool(IN PEPROCESS Process,
78 IN UCHAR PoolIndex,
79 IN SIZE_T Amount)
80 {
81 ASSERT(Process);
82 ASSERT(Process != PsInitialSystemProcess);
83 ASSERT(PoolIndex <= 2);
84 ASSERT(!(Amount & 0x80000000)); /* we need to be able to negate it */
85 if (Process->QuotaUsage[PoolIndex] < Amount)
86 {
87 DPRINT1("WARNING: Process->QuotaUsage sanity check failed.\n");
88 }
89 else
90 {
91 InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex],
92 -(LONG)Amount);
93 }
94 }
95
96 /* FUNCTIONS ***************************************************************/
97
98 VOID
99 NTAPI
100 INIT_FUNCTION
101 PsInitializeQuotaSystem(VOID)
102 {
103 RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock));
104 PspDefaultQuotaBlock.QuotaEntry[PagedPool].Limit = (SIZE_T)-1;
105 PspDefaultQuotaBlock.QuotaEntry[NonPagedPool].Limit = (SIZE_T)-1;
106 PspDefaultQuotaBlock.QuotaEntry[2].Limit = (SIZE_T)-1; /* Page file */
107 PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock;
108 }
109
110 VOID
111 NTAPI
112 PspInheritQuota(PEPROCESS Process, PEPROCESS ParentProcess)
113 {
114 if (ParentProcess != NULL)
115 {
116 PEPROCESS_QUOTA_BLOCK QuotaBlock = ParentProcess->QuotaBlock;
117
118 ASSERT(QuotaBlock != NULL);
119
120 (void)InterlockedIncrementUL(&QuotaBlock->ReferenceCount);
121
122 Process->QuotaBlock = QuotaBlock;
123 }
124 else
125 Process->QuotaBlock = &PspDefaultQuotaBlock;
126 }
127
128 VOID
129 NTAPI
130 PspInsertQuotaBlock(
131 PEPROCESS_QUOTA_BLOCK QuotaBlock)
132 {
133 KIRQL OldIrql;
134
135 OldIrql = KfAcquireSpinLock(&PspQuotaLock);
136 InsertTailList(&PspQuotaBlockList, &QuotaBlock->QuotaList);
137 KfReleaseSpinLock(&PspQuotaLock, OldIrql);
138 }
139
140 VOID
141 NTAPI
142 PspDestroyQuotaBlock(PEPROCESS Process)
143 {
144 PEPROCESS_QUOTA_BLOCK QuotaBlock = Process->QuotaBlock;
145 KIRQL OldIrql;
146
147 if (QuotaBlock != &PspDefaultQuotaBlock &&
148 InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0)
149 {
150 OldIrql = KfAcquireSpinLock(&PspQuotaLock);
151 RemoveEntryList(&QuotaBlock->QuotaList);
152 KfReleaseSpinLock(&PspQuotaLock, OldIrql);
153 ExFreePool(QuotaBlock);
154 }
155 }
156
157 /*
158 * @implemented
159 */
160 NTSTATUS
161 NTAPI
162 PsChargeProcessPageFileQuota(IN PEPROCESS Process,
163 IN SIZE_T Amount)
164 {
165 /* Don't do anything for the system process */
166 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
167
168 return PspChargeProcessQuotaSpecifiedPool(Process, 2, Amount);
169 }
170
171 /*
172 * @implemented
173 */
174 VOID
175 NTAPI
176 PsChargePoolQuota(IN PEPROCESS Process,
177 IN POOL_TYPE PoolType,
178 IN SIZE_T Amount)
179 {
180 NTSTATUS Status;
181 ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
182
183 /* Don't do anything for the system process */
184 if (Process == PsInitialSystemProcess) return;
185
186 /* Charge the usage */
187 Status = PsChargeProcessPoolQuota(Process, PoolType, Amount);
188 if (!NT_SUCCESS(Status)) ExRaiseStatus(Status);
189 }
190
191 /*
192 * @implemented
193 */
194 NTSTATUS
195 NTAPI
196 PsChargeProcessNonPagedPoolQuota(IN PEPROCESS Process,
197 IN SIZE_T Amount)
198 {
199 /* Call the general function */
200 return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount);
201 }
202
203 /*
204 * @implemented
205 */
206 NTSTATUS
207 NTAPI
208 PsChargeProcessPagedPoolQuota(IN PEPROCESS Process,
209 IN SIZE_T Amount)
210 {
211 /* Call the general function */
212 return PsChargeProcessPoolQuota(Process, PagedPool, Amount);
213 }
214
215 /*
216 * @implemented
217 */
218 NTSTATUS
219 NTAPI
220 PsChargeProcessPoolQuota(IN PEPROCESS Process,
221 IN POOL_TYPE PoolType,
222 IN SIZE_T Amount)
223 {
224 /* Don't do anything for the system process */
225 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
226
227 return PspChargeProcessQuotaSpecifiedPool(Process,
228 (PoolType & PAGED_POOL_MASK),
229 Amount);
230 }
231
232 /*
233 * @implemented
234 */
235 VOID
236 NTAPI
237 PsReturnPoolQuota(IN PEPROCESS Process,
238 IN POOL_TYPE PoolType,
239 IN SIZE_T Amount)
240 {
241 /* Don't do anything for the system process */
242 if (Process == PsInitialSystemProcess) return;
243
244 PspReturnProcessQuotaSpecifiedPool(Process,
245 (PoolType & PAGED_POOL_MASK),
246 Amount);
247 }
248
249 /*
250 * @implemented
251 */
252 VOID
253 NTAPI
254 PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process,
255 IN SIZE_T Amount)
256 {
257 /* Don't do anything for the system process */
258 if (Process == PsInitialSystemProcess) return;
259
260 PsReturnPoolQuota(Process, NonPagedPool, Amount);
261 }
262
263 /*
264 * @implemented
265 */
266 VOID
267 NTAPI
268 PsReturnProcessPagedPoolQuota(IN PEPROCESS Process,
269 IN SIZE_T Amount)
270 {
271 /* Don't do anything for the system process */
272 if (Process == PsInitialSystemProcess) return;
273
274 PsReturnPoolQuota(Process, PagedPool, Amount);
275 }
276
277 /*
278 * @implemented
279 */
280 NTSTATUS
281 NTAPI
282 PsReturnProcessPageFileQuota(IN PEPROCESS Process,
283 IN SIZE_T Amount)
284 {
285 /* Don't do anything for the system process */
286 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
287
288 PspReturnProcessQuotaSpecifiedPool(Process, 2, Amount);
289 return STATUS_SUCCESS;
290 }
291
292 NTSTATUS
293 NTAPI
294 PspSetQuotaLimits(
295 _In_ HANDLE ProcessHandle,
296 _In_ ULONG Unused,
297 _In_ PVOID QuotaLimits,
298 _In_ ULONG QuotaLimitsLength,
299 _In_ KPROCESSOR_MODE PreviousMode)
300 {
301 QUOTA_LIMITS_EX CapturedQuotaLimits;
302 PEPROCESS Process;
303 PEPROCESS_QUOTA_BLOCK QuotaBlock, OldQuotaBlock;
304 BOOLEAN IncreaseOkay;
305 KAPC_STATE SavedApcState;
306 NTSTATUS Status;
307
308 UNREFERENCED_PARAMETER(Unused);
309
310 _SEH2_TRY
311 {
312 ProbeForRead(QuotaLimits, QuotaLimitsLength, sizeof(ULONG));
313
314 /* Check if we have the basic or extended structure */
315 if (QuotaLimitsLength == sizeof(QUOTA_LIMITS))
316 {
317 /* Copy the basic structure, zero init the remaining fields */
318 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS));
319 CapturedQuotaLimits.WorkingSetLimit = 0;
320 CapturedQuotaLimits.Reserved2 = 0;
321 CapturedQuotaLimits.Reserved3 = 0;
322 CapturedQuotaLimits.Reserved4 = 0;
323 CapturedQuotaLimits.CpuRateLimit.RateData = 0;
324 CapturedQuotaLimits.Flags = 0;
325 }
326 else if (QuotaLimitsLength == sizeof(QUOTA_LIMITS_EX))
327 {
328 /* Copy the full structure */
329 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS_EX));
330
331 /* Verify that the caller passed valid flags */
332 if ((CapturedQuotaLimits.Flags & ~VALID_QUOTA_FLAGS) ||
333 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_ENABLE) &&
334 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_DISABLE)) ||
335 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_ENABLE) &&
336 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_DISABLE)))
337 {
338 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits.Flags);
339 return STATUS_INVALID_PARAMETER;
340 }
341
342 /* Verify that the caller didn't pass reserved values */
343 if ((CapturedQuotaLimits.WorkingSetLimit != 0) ||
344 (CapturedQuotaLimits.Reserved2 != 0) ||
345 (CapturedQuotaLimits.Reserved3 != 0) ||
346 (CapturedQuotaLimits.Reserved4 != 0) ||
347 (CapturedQuotaLimits.CpuRateLimit.RateData != 0))
348 {
349 DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n",
350 CapturedQuotaLimits.WorkingSetLimit,
351 CapturedQuotaLimits.Reserved2,
352 CapturedQuotaLimits.Reserved3,
353 CapturedQuotaLimits.Reserved4,
354 CapturedQuotaLimits.CpuRateLimit.RateData);
355 return STATUS_INVALID_PARAMETER;
356 }
357 }
358 else
359 {
360 DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength);
361 return STATUS_INFO_LENGTH_MISMATCH;
362 }
363 }
364 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
365 {
366 DPRINT1("Exception while copying data\n");
367 return _SEH2_GetExceptionCode();
368 }
369 _SEH2_END;
370
371 /* Reference the process */
372 Status = ObReferenceObjectByHandle(ProcessHandle,
373 PROCESS_SET_QUOTA,
374 PsProcessType,
375 PreviousMode,
376 (PVOID*)&Process,
377 NULL);
378 if (!NT_SUCCESS(Status))
379 {
380 DPRINT1("Failed to reference process handle: 0x%lx\n", Status);
381 return Status;
382 }
383
384 /* Check the caller changes the working set size limits */
385 if ((CapturedQuotaLimits.MinimumWorkingSetSize != 0) &&
386 (CapturedQuotaLimits.MaximumWorkingSetSize != 0))
387 {
388 /* Check for special case: trimming the WS */
389 if ((CapturedQuotaLimits.MinimumWorkingSetSize == SIZE_T_MAX) &&
390 (CapturedQuotaLimits.MaximumWorkingSetSize == SIZE_T_MAX))
391 {
392 /* No increase allowed */
393 IncreaseOkay = FALSE;
394 }
395 else
396 {
397 /* Check if the caller has the required privilege */
398 IncreaseOkay = SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege,
399 PreviousMode);
400 }
401
402 /* Attach to the target process and disable APCs */
403 KeStackAttachProcess(&Process->Pcb, &SavedApcState);
404 KeEnterGuardedRegion();
405
406 /* Call Mm to adjust the process' working set size */
407 Status = MmAdjustWorkingSetSize(CapturedQuotaLimits.MinimumWorkingSetSize,
408 CapturedQuotaLimits.MaximumWorkingSetSize,
409 0,
410 IncreaseOkay);
411
412 /* Bring back APCs and detach from the process */
413 KeLeaveGuardedRegion();
414 KeUnstackDetachProcess(&SavedApcState);
415 }
416 else if (Process->QuotaBlock == &PspDefaultQuotaBlock)
417 {
418 /* Check if the caller has the required privilege */
419 if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, PreviousMode))
420 {
421 ObDereferenceObject(Process);
422 return STATUS_PRIVILEGE_NOT_HELD;
423 }
424
425 /* Allocate a new quota block */
426 QuotaBlock = ExAllocatePoolWithTag(NonPagedPool,
427 sizeof(EPROCESS_QUOTA_BLOCK),
428 TAG_QUOTA_BLOCK);
429 if (QuotaBlock == NULL)
430 {
431 ObDereferenceObject(Process);
432 return STATUS_NO_MEMORY;
433 }
434
435 /* Initialize the quota block */
436 QuotaBlock->ReferenceCount = 1;
437 QuotaBlock->ProcessCount = 1;
438 QuotaBlock->QuotaEntry[0].Peak = Process->QuotaPeak[0];
439 QuotaBlock->QuotaEntry[1].Peak = Process->QuotaPeak[1];
440 QuotaBlock->QuotaEntry[2].Peak = Process->QuotaPeak[2];
441 QuotaBlock->QuotaEntry[0].Limit = PspDefaultQuotaBlock.QuotaEntry[0].Limit;
442 QuotaBlock->QuotaEntry[1].Limit = PspDefaultQuotaBlock.QuotaEntry[1].Limit;
443 QuotaBlock->QuotaEntry[2].Limit = PspDefaultQuotaBlock.QuotaEntry[2].Limit;
444
445 /* Try to exchange the quota block, if that failed, just drop it */
446 OldQuotaBlock = InterlockedCompareExchangePointer(&Process->QuotaBlock,
447 QuotaBlock,
448 &PspDefaultQuotaBlock);
449 if (OldQuotaBlock == &PspDefaultQuotaBlock)
450 {
451 /* Success, insert the new quota block */
452 PspInsertQuotaBlock(QuotaBlock);
453 }
454 else
455 {
456 /* Failed, free the quota block and ignore it */
457 ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK);
458 }
459
460 Status = STATUS_SUCCESS;
461 }
462
463 /* Dereference the process and return the status */
464 ObDereferenceObject(Process);
465 return Status;
466 }
467
468
469 /* EOF */