2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ps/quota.c
5 * PURPOSE: Process Pool Quotas
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
11 /* INCLUDES **************************************************************/
14 #include <ntintsafe.h>
18 EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock
;
19 static LIST_ENTRY PspQuotaBlockList
= {&PspQuotaBlockList
, &PspQuotaBlockList
};
20 static KSPIN_LOCK PspQuotaLock
;
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)
28 /* PRIVATE FUNCTIONS *******************************************************/
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.
40 PspChargeProcessQuotaSpecifiedPool(IN PEPROCESS Process
,
45 ASSERT(Process
!= PsInitialSystemProcess
);
46 ASSERT(PoolIndex
<= 2);
47 ASSERT(Process
->QuotaBlock
);
49 /* Note: Race warning. TODO: Needs to add/use lock for this */
50 if (Process
->QuotaUsage
[PoolIndex
] + Amount
>
51 Process
->QuotaBlock
->QuotaEntry
[PoolIndex
].Limit
)
53 DPRINT1("Quota exceeded, but ROS will let it slide...\n");
54 return STATUS_SUCCESS
;
55 //return STATUS_QUOTA_EXCEEDED; /* caller raises the exception */
58 InterlockedExchangeAdd((LONG
*)&Process
->QuotaUsage
[PoolIndex
], Amount
);
60 /* Note: Race warning. TODO: Needs to add/use lock for this */
61 if (Process
->QuotaPeak
[PoolIndex
] < Process
->QuotaUsage
[PoolIndex
])
63 Process
->QuotaPeak
[PoolIndex
] = Process
->QuotaUsage
[PoolIndex
];
66 return STATUS_SUCCESS
;
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.
77 PspReturnProcessQuotaSpecifiedPool(IN PEPROCESS 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
)
87 DPRINT1("WARNING: Process->QuotaUsage sanity check failed.\n");
91 InterlockedExchangeAdd((LONG
*)&Process
->QuotaUsage
[PoolIndex
],
96 /* FUNCTIONS ***************************************************************/
101 PsInitializeQuotaSystem(VOID
)
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
;
112 PspInheritQuota(PEPROCESS Process
, PEPROCESS ParentProcess
)
114 if (ParentProcess
!= NULL
)
116 PEPROCESS_QUOTA_BLOCK QuotaBlock
= ParentProcess
->QuotaBlock
;
118 ASSERT(QuotaBlock
!= NULL
);
120 (void)InterlockedIncrementUL(&QuotaBlock
->ReferenceCount
);
122 Process
->QuotaBlock
= QuotaBlock
;
125 Process
->QuotaBlock
= &PspDefaultQuotaBlock
;
131 PEPROCESS_QUOTA_BLOCK QuotaBlock
)
135 KeAcquireSpinLock(&PspQuotaLock
, &OldIrql
);
136 InsertTailList(&PspQuotaBlockList
, &QuotaBlock
->QuotaList
);
137 KeReleaseSpinLock(&PspQuotaLock
, OldIrql
);
142 PspDestroyQuotaBlock(PEPROCESS Process
)
144 PEPROCESS_QUOTA_BLOCK QuotaBlock
= Process
->QuotaBlock
;
147 if (QuotaBlock
!= &PspDefaultQuotaBlock
&&
148 InterlockedDecrementUL(&QuotaBlock
->ReferenceCount
) == 0)
150 KeAcquireSpinLock(&PspQuotaLock
, &OldIrql
);
151 RemoveEntryList(&QuotaBlock
->QuotaList
);
152 KeReleaseSpinLock(&PspQuotaLock
, OldIrql
);
153 ExFreePool(QuotaBlock
);
162 PsChargeProcessPageFileQuota(IN PEPROCESS Process
,
165 /* Don't do anything for the system process */
166 if (Process
== PsInitialSystemProcess
) return STATUS_SUCCESS
;
168 return PspChargeProcessQuotaSpecifiedPool(Process
, 2, Amount
);
176 PsChargePoolQuota(IN PEPROCESS Process
,
177 IN POOL_TYPE PoolType
,
181 ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL
);
183 /* Don't do anything for the system process */
184 if (Process
== PsInitialSystemProcess
) return;
186 /* Charge the usage */
187 Status
= PsChargeProcessPoolQuota(Process
, PoolType
, Amount
);
188 if (!NT_SUCCESS(Status
)) ExRaiseStatus(Status
);
196 PsChargeProcessNonPagedPoolQuota(IN PEPROCESS Process
,
199 /* Call the general function */
200 return PsChargeProcessPoolQuota(Process
, NonPagedPool
, Amount
);
208 PsChargeProcessPagedPoolQuota(IN PEPROCESS Process
,
211 /* Call the general function */
212 return PsChargeProcessPoolQuota(Process
, PagedPool
, Amount
);
220 PsChargeProcessPoolQuota(IN PEPROCESS Process
,
221 IN POOL_TYPE PoolType
,
224 /* Don't do anything for the system process */
225 if (Process
== PsInitialSystemProcess
) return STATUS_SUCCESS
;
227 return PspChargeProcessQuotaSpecifiedPool(Process
,
228 (PoolType
& PAGED_POOL_MASK
),
237 PsReturnPoolQuota(IN PEPROCESS Process
,
238 IN POOL_TYPE PoolType
,
241 /* Don't do anything for the system process */
242 if (Process
== PsInitialSystemProcess
) return;
244 PspReturnProcessQuotaSpecifiedPool(Process
,
245 (PoolType
& PAGED_POOL_MASK
),
254 PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process
,
257 /* Don't do anything for the system process */
258 if (Process
== PsInitialSystemProcess
) return;
260 PsReturnPoolQuota(Process
, NonPagedPool
, Amount
);
268 PsReturnProcessPagedPoolQuota(IN PEPROCESS Process
,
271 /* Don't do anything for the system process */
272 if (Process
== PsInitialSystemProcess
) return;
274 PsReturnPoolQuota(Process
, PagedPool
, Amount
);
282 PsReturnProcessPageFileQuota(IN PEPROCESS Process
,
285 /* Don't do anything for the system process */
286 if (Process
== PsInitialSystemProcess
) return STATUS_SUCCESS
;
288 PspReturnProcessQuotaSpecifiedPool(Process
, 2, Amount
);
289 return STATUS_SUCCESS
;
295 _In_ PEPROCESS Process
,
297 _In_ PVOID QuotaLimits
,
298 _In_ ULONG QuotaLimitsLength
,
299 _In_ KPROCESSOR_MODE PreviousMode
)
301 QUOTA_LIMITS_EX CapturedQuotaLimits
;
302 PEPROCESS_QUOTA_BLOCK QuotaBlock
, OldQuotaBlock
;
303 BOOLEAN IncreaseOkay
;
304 KAPC_STATE SavedApcState
;
307 UNREFERENCED_PARAMETER(Unused
);
311 ProbeForRead(QuotaLimits
, QuotaLimitsLength
, sizeof(ULONG
));
313 /* Check if we have the basic or extended structure */
314 if (QuotaLimitsLength
== sizeof(QUOTA_LIMITS
))
316 /* Copy the basic structure, zero init the remaining fields */
317 RtlCopyMemory(&CapturedQuotaLimits
, QuotaLimits
, sizeof(QUOTA_LIMITS
));
318 CapturedQuotaLimits
.WorkingSetLimit
= 0;
319 CapturedQuotaLimits
.Reserved2
= 0;
320 CapturedQuotaLimits
.Reserved3
= 0;
321 CapturedQuotaLimits
.Reserved4
= 0;
322 CapturedQuotaLimits
.CpuRateLimit
.RateData
= 0;
323 CapturedQuotaLimits
.Flags
= 0;
325 else if (QuotaLimitsLength
== sizeof(QUOTA_LIMITS_EX
))
327 /* Copy the full structure */
328 RtlCopyMemory(&CapturedQuotaLimits
, QuotaLimits
, sizeof(QUOTA_LIMITS_EX
));
330 /* Verify that the caller passed valid flags */
331 if ((CapturedQuotaLimits
.Flags
& ~VALID_QUOTA_FLAGS
) ||
332 ((CapturedQuotaLimits
.Flags
& QUOTA_LIMITS_HARDWS_MIN_ENABLE
) &&
333 (CapturedQuotaLimits
.Flags
& QUOTA_LIMITS_HARDWS_MIN_DISABLE
)) ||
334 ((CapturedQuotaLimits
.Flags
& QUOTA_LIMITS_HARDWS_MAX_ENABLE
) &&
335 (CapturedQuotaLimits
.Flags
& QUOTA_LIMITS_HARDWS_MAX_DISABLE
)))
337 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits
.Flags
);
338 _SEH2_YIELD(return STATUS_INVALID_PARAMETER
);
341 /* Verify that the caller didn't pass reserved values */
342 if ((CapturedQuotaLimits
.WorkingSetLimit
!= 0) ||
343 (CapturedQuotaLimits
.Reserved2
!= 0) ||
344 (CapturedQuotaLimits
.Reserved3
!= 0) ||
345 (CapturedQuotaLimits
.Reserved4
!= 0) ||
346 (CapturedQuotaLimits
.CpuRateLimit
.RateData
!= 0))
348 DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n",
349 CapturedQuotaLimits
.WorkingSetLimit
,
350 CapturedQuotaLimits
.Reserved2
,
351 CapturedQuotaLimits
.Reserved3
,
352 CapturedQuotaLimits
.Reserved4
,
353 CapturedQuotaLimits
.CpuRateLimit
.RateData
);
354 _SEH2_YIELD(return STATUS_INVALID_PARAMETER
);
359 DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength
);
360 _SEH2_YIELD(return STATUS_INFO_LENGTH_MISMATCH
);
363 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
365 DPRINT1("Exception while copying data\n");
366 _SEH2_YIELD(return _SEH2_GetExceptionCode());
370 /* Check the caller changes the working set size limits */
371 if ((CapturedQuotaLimits
.MinimumWorkingSetSize
!= 0) &&
372 (CapturedQuotaLimits
.MaximumWorkingSetSize
!= 0))
374 /* Check for special case: trimming the WS */
375 if ((CapturedQuotaLimits
.MinimumWorkingSetSize
== SIZE_T_MAX
) &&
376 (CapturedQuotaLimits
.MaximumWorkingSetSize
== SIZE_T_MAX
))
378 /* No increase allowed */
379 IncreaseOkay
= FALSE
;
383 /* Check if the caller has the required privilege */
384 IncreaseOkay
= SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege
,
388 /* Attach to the target process and disable APCs */
389 KeStackAttachProcess(&Process
->Pcb
, &SavedApcState
);
390 KeEnterGuardedRegion();
392 /* Call Mm to adjust the process' working set size */
393 Status
= MmAdjustWorkingSetSize(CapturedQuotaLimits
.MinimumWorkingSetSize
,
394 CapturedQuotaLimits
.MaximumWorkingSetSize
,
398 /* Bring back APCs and detach from the process */
399 KeLeaveGuardedRegion();
400 KeUnstackDetachProcess(&SavedApcState
);
402 else if (Process
->QuotaBlock
== &PspDefaultQuotaBlock
)
404 /* Check if the caller has the required privilege */
405 if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege
, PreviousMode
))
407 return STATUS_PRIVILEGE_NOT_HELD
;
410 /* Allocate a new quota block */
411 QuotaBlock
= ExAllocatePoolWithTag(NonPagedPool
,
412 sizeof(EPROCESS_QUOTA_BLOCK
),
414 if (QuotaBlock
== NULL
)
416 ObDereferenceObject(Process
);
417 return STATUS_NO_MEMORY
;
420 /* Initialize the quota block */
421 QuotaBlock
->ReferenceCount
= 1;
422 QuotaBlock
->ProcessCount
= 1;
423 QuotaBlock
->QuotaEntry
[0].Peak
= Process
->QuotaPeak
[0];
424 QuotaBlock
->QuotaEntry
[1].Peak
= Process
->QuotaPeak
[1];
425 QuotaBlock
->QuotaEntry
[2].Peak
= Process
->QuotaPeak
[2];
426 QuotaBlock
->QuotaEntry
[0].Limit
= PspDefaultQuotaBlock
.QuotaEntry
[0].Limit
;
427 QuotaBlock
->QuotaEntry
[1].Limit
= PspDefaultQuotaBlock
.QuotaEntry
[1].Limit
;
428 QuotaBlock
->QuotaEntry
[2].Limit
= PspDefaultQuotaBlock
.QuotaEntry
[2].Limit
;
430 /* Try to exchange the quota block, if that failed, just drop it */
431 OldQuotaBlock
= InterlockedCompareExchangePointer((PVOID
*)&Process
->QuotaBlock
,
433 &PspDefaultQuotaBlock
);
434 if (OldQuotaBlock
== &PspDefaultQuotaBlock
)
436 /* Success, insert the new quota block */
437 PspInsertQuotaBlock(QuotaBlock
);
441 /* Failed, free the quota block and ignore it */
442 ExFreePoolWithTag(QuotaBlock
, TAG_QUOTA_BLOCK
);
445 Status
= STATUS_SUCCESS
;
449 Status
= STATUS_SUCCESS
;