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_ HANDLE ProcessHandle
,
297 _In_ PVOID QuotaLimits
,
298 _In_ ULONG QuotaLimitsLength
,
299 _In_ KPROCESSOR_MODE PreviousMode
)
301 QUOTA_LIMITS_EX CapturedQuotaLimits
;
303 PEPROCESS_QUOTA_BLOCK QuotaBlock
, OldQuotaBlock
;
304 BOOLEAN IncreaseOkay
;
305 KAPC_STATE SavedApcState
;
308 UNREFERENCED_PARAMETER(Unused
);
312 ProbeForRead(QuotaLimits
, QuotaLimitsLength
, sizeof(ULONG
));
314 /* Check if we have the basic or extended structure */
315 if (QuotaLimitsLength
== sizeof(QUOTA_LIMITS
))
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;
326 else if (QuotaLimitsLength
== sizeof(QUOTA_LIMITS_EX
))
328 /* Copy the full structure */
329 RtlCopyMemory(&CapturedQuotaLimits
, QuotaLimits
, sizeof(QUOTA_LIMITS_EX
));
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
)))
338 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits
.Flags
);
339 return STATUS_INVALID_PARAMETER
;
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))
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
;
360 DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength
);
361 return STATUS_INFO_LENGTH_MISMATCH
;
364 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
366 DPRINT1("Exception while copying data\n");
367 return _SEH2_GetExceptionCode();
371 /* Reference the process */
372 Status
= ObReferenceObjectByHandle(ProcessHandle
,
378 if (!NT_SUCCESS(Status
))
380 DPRINT1("Failed to reference process handle: 0x%lx\n", Status
);
384 /* Check the caller changes the working set size limits */
385 if ((CapturedQuotaLimits
.MinimumWorkingSetSize
!= 0) &&
386 (CapturedQuotaLimits
.MaximumWorkingSetSize
!= 0))
388 /* Check for special case: trimming the WS */
389 if ((CapturedQuotaLimits
.MinimumWorkingSetSize
== SIZE_T_MAX
) &&
390 (CapturedQuotaLimits
.MaximumWorkingSetSize
== SIZE_T_MAX
))
392 /* No increase allowed */
393 IncreaseOkay
= FALSE
;
397 /* Check if the caller has the required privilege */
398 IncreaseOkay
= SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege
,
402 /* Attach to the target process and disable APCs */
403 KeStackAttachProcess(&Process
->Pcb
, &SavedApcState
);
404 KeEnterGuardedRegion();
406 /* Call Mm to adjust the process' working set size */
407 Status
= MmAdjustWorkingSetSize(CapturedQuotaLimits
.MinimumWorkingSetSize
,
408 CapturedQuotaLimits
.MaximumWorkingSetSize
,
412 /* Bring back APCs and detach from the process */
413 KeLeaveGuardedRegion();
414 KeUnstackDetachProcess(&SavedApcState
);
416 else if (Process
->QuotaBlock
== &PspDefaultQuotaBlock
)
418 /* Check if the caller has the required privilege */
419 if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege
, PreviousMode
))
421 ObDereferenceObject(Process
);
422 return STATUS_PRIVILEGE_NOT_HELD
;
425 /* Allocate a new quota block */
426 QuotaBlock
= ExAllocatePoolWithTag(NonPagedPool
,
427 sizeof(EPROCESS_QUOTA_BLOCK
),
429 if (QuotaBlock
== NULL
)
431 ObDereferenceObject(Process
);
432 return STATUS_NO_MEMORY
;
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
;
445 /* Try to exchange the quota block, if that failed, just drop it */
446 OldQuotaBlock
= InterlockedCompareExchangePointer(&Process
->QuotaBlock
,
448 &PspDefaultQuotaBlock
);
449 if (OldQuotaBlock
== &PspDefaultQuotaBlock
)
451 /* Success, insert the new quota block */
452 PspInsertQuotaBlock(QuotaBlock
);
456 /* Failed, free the quota block and ignore it */
457 ExFreePoolWithTag(QuotaBlock
, TAG_QUOTA_BLOCK
);
460 Status
= STATUS_SUCCESS
;
463 /* Dereference the process and return the status */
464 ObDereferenceObject(Process
);