2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ps/job.c
5 * PURPOSE: Job Native Functions
6 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) (stubs)
7 * Thomas Weidenmueller <w3seek@reactos.com>
8 * Pierre Schweitzer (pierre@reactos.org)
11 /* INCLUDES *****************************************************************/
18 /* GLOBALS *******************************************************************/
20 POBJECT_TYPE PsJobType
= NULL
;
22 LIST_ENTRY PsJobListHead
;
23 static FAST_MUTEX PsJobListLock
;
25 BOOLEAN PspUseJobSchedulingClasses
;
27 CHAR PspJobSchedulingClasses
[PSP_JOB_SCHEDULING_CLASSES
] =
41 GENERIC_MAPPING PspJobMapping
=
43 STANDARD_RIGHTS_READ
| JOB_OBJECT_QUERY
,
45 STANDARD_RIGHTS_WRITE
| JOB_OBJECT_ASSIGN_PROCESS
|
46 JOB_OBJECT_SET_ATTRIBUTES
| JOB_OBJECT_TERMINATE
,
48 STANDARD_RIGHTS_EXECUTE
| SYNCHRONIZE
,
50 STANDARD_RIGHTS_ALL
| THREAD_ALL_ACCESS
// bug fixed only in vista
53 ULONG PspJobInfoLengths
[] =
56 sizeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
),
57 sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION
),
58 sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST
),
59 sizeof(JOBOBJECT_BASIC_UI_RESTRICTIONS
),
60 sizeof(JOBOBJECT_SECURITY_LIMIT_INFORMATION
),
61 sizeof(JOBOBJECT_END_OF_JOB_TIME_INFORMATION
),
62 sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT
),
63 sizeof(JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION
),
64 sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION
),
68 ULONG PspJobInfoAlign
[] =
83 /* FUNCTIONS *****************************************************************/
87 PspDeleteJob ( PVOID ObjectBody
)
89 PEJOB Job
= (PEJOB
)ObjectBody
;
91 /* remove the reference to the completion port if associated */
92 if(Job
->CompletionPort
!= NULL
)
94 ObDereferenceObject(Job
->CompletionPort
);
97 /* unlink the job object */
98 if(Job
->JobLinks
.Flink
!= NULL
)
100 ExAcquireFastMutex(&PsJobListLock
);
101 RemoveEntryList(&Job
->JobLinks
);
102 ExReleaseFastMutex(&PsJobListLock
);
105 ExDeleteResource(&Job
->JobLock
);
111 PspInitializeJobStructures(VOID
)
113 InitializeListHead(&PsJobListHead
);
114 ExInitializeFastMutex(&PsJobListLock
);
119 PspAssignProcessToJob(PEPROCESS Process
,
122 DPRINT("PspAssignProcessToJob() is unimplemented!\n");
123 return STATUS_NOT_IMPLEMENTED
;
128 PspTerminateJobObject(PEJOB Job
,
129 KPROCESSOR_MODE AccessMode
,
130 NTSTATUS ExitStatus
)
132 DPRINT("PspTerminateJobObject() is unimplemented!\n");
133 return STATUS_NOT_IMPLEMENTED
;
138 PspRemoveProcessFromJob(IN PEPROCESS Process
,
146 PspExitProcessFromJob(IN PEJOB Job
,
147 IN PEPROCESS Process
)
157 NtAssignProcessToJobObject (
159 HANDLE ProcessHandle
)
162 KPROCESSOR_MODE PreviousMode
;
167 PreviousMode
= ExGetPreviousMode();
169 /* make sure we're having a handle with enough rights, especially the to
170 terminate the process. otherwise one could abuse the job objects to
171 terminate processes without having rights granted to do so! The reason
172 I open the process handle before the job handle is that a simple test showed
173 that it first complains about a invalid process handle! The other way around
174 would be simpler though... */
175 Status
= ObReferenceObjectByHandle(
182 if(NT_SUCCESS(Status
))
184 if(Process
->Job
== NULL
)
188 Status
= ObReferenceObjectByHandle(
190 JOB_OBJECT_ASSIGN_PROCESS
,
195 if(NT_SUCCESS(Status
))
197 /* lock the process so we can safely assign the process. Note that in the
198 meanwhile another thread could have assigned this process to a job! */
200 if(ExAcquireRundownProtection(&Process
->RundownProtect
))
202 if(Process
->Job
== NULL
&& PsGetProcessSessionId(Process
) == Job
->SessionId
)
204 /* Just store the pointer to the job object in the process, we'll
205 assign it later. The reason we can't do this here is that locking
206 the job object might require it to wait, which is a bad thing
207 while holding the process lock! */
209 ObReferenceObject(Job
);
213 /* process is already assigned to a job or session id differs! */
214 Status
= STATUS_ACCESS_DENIED
;
216 ExReleaseRundownProtection(&Process
->RundownProtect
);
218 if(NT_SUCCESS(Status
))
220 /* let's actually assign the process to the job as we're not holding
221 the process lock anymore! */
222 Status
= PspAssignProcessToJob(Process
, Job
);
226 ObDereferenceObject(Job
);
231 /* process is already assigned to a job or session id differs! */
232 Status
= STATUS_ACCESS_DENIED
;
235 ObDereferenceObject(Process
);
243 NtCreateJobSet(IN ULONG NumJob
,
244 IN PJOB_SET_ARRAY UserJobSet
,
248 return STATUS_NOT_IMPLEMENTED
;
258 ACCESS_MASK DesiredAccess
,
259 POBJECT_ATTRIBUTES ObjectAttributes
)
263 KPROCESSOR_MODE PreviousMode
;
264 PEPROCESS CurrentProcess
;
269 PreviousMode
= ExGetPreviousMode();
270 CurrentProcess
= PsGetCurrentProcess();
272 /* check for valid buffers */
273 if (PreviousMode
!= KernelMode
)
277 ProbeForWriteHandle(JobHandle
);
279 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
281 _SEH2_YIELD(return _SEH2_GetExceptionCode());
286 Status
= ObCreateObject(PreviousMode
,
296 if(NT_SUCCESS(Status
))
298 /* FIXME - Zero all fields as we don't yet implement all of them */
299 RtlZeroMemory(Job
, sizeof(EJOB
));
301 /* make sure that early destruction doesn't attempt to remove the object from
302 the list before it even gets added! */
303 Job
->JobLinks
.Flink
= NULL
;
305 /* setup the job object - FIXME: More to do! */
306 InitializeListHead(&Job
->JobSetLinks
);
307 InitializeListHead(&Job
->ProcessListHead
);
309 /* inherit the session id from the caller */
310 Job
->SessionId
= PsGetProcessSessionId(CurrentProcess
);
312 KeInitializeGuardedMutex(&Job
->MemoryLimitsLock
);
314 Status
= ExInitializeResource(&Job
->JobLock
);
315 if(!NT_SUCCESS(Status
))
317 DPRINT1("Failed to initialize job lock!!!\n");
318 ObDereferenceObject(Job
);
321 KeInitializeEvent(&Job
->Event
, NotificationEvent
, FALSE
);
323 /* link the object into the global job list */
324 ExAcquireFastMutex(&PsJobListLock
);
325 InsertTailList(&PsJobListHead
, &Job
->JobLinks
);
326 ExReleaseFastMutex(&PsJobListLock
);
328 Status
= ObInsertObject(Job
,
334 if(NT_SUCCESS(Status
))
336 /* pass the handle back to the caller */
339 /* NOTE: if the caller passed invalid buffers to receive the handle it's his
340 own fault! the object will still be created and live... It's possible
341 to find the handle using ObFindHandleForObject()! */
344 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
346 Status
= _SEH2_GetExceptionCode();
362 IN HANDLE ProcessHandle
,
363 IN HANDLE JobHandle OPTIONAL
)
365 KPROCESSOR_MODE PreviousMode
;
369 PreviousMode
= ExGetPreviousMode();
373 Status
= ObReferenceObjectByHandle(
375 PROCESS_QUERY_INFORMATION
,
380 if(NT_SUCCESS(Status
))
382 /* FIXME - make sure the job object doesn't get exchanged or deleted while trying to
383 reference it, e.g. by locking it somehow until it is referenced... */
385 PEJOB ProcessJob
= Process
->Job
;
387 if(ProcessJob
!= NULL
)
389 if(JobHandle
== NULL
)
391 /* the process is assigned to a job */
392 Status
= STATUS_PROCESS_IN_JOB
;
394 else /* JobHandle != NULL */
398 /* get the job object and compare the object pointer with the one assigned to the process */
399 Status
= ObReferenceObjectByHandle(JobHandle
,
405 if(NT_SUCCESS(Status
))
407 Status
= ((ProcessJob
== JobObject
) ? STATUS_PROCESS_IN_JOB
: STATUS_PROCESS_NOT_IN_JOB
);
408 ObDereferenceObject(JobObject
);
414 /* the process is not assigned to any job */
415 Status
= STATUS_PROCESS_NOT_IN_JOB
;
417 ObDereferenceObject(Process
);
431 ACCESS_MASK DesiredAccess
,
432 POBJECT_ATTRIBUTES ObjectAttributes
)
434 KPROCESSOR_MODE PreviousMode
;
440 PreviousMode
= ExGetPreviousMode();
442 /* check for valid buffers */
443 if (PreviousMode
!= KernelMode
)
447 ProbeForWriteHandle(JobHandle
);
449 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
451 _SEH2_YIELD(return _SEH2_GetExceptionCode());
456 Status
= ObOpenObjectByName(ObjectAttributes
,
463 if(NT_SUCCESS(Status
))
469 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
471 Status
= _SEH2_GetExceptionCode();
485 NtQueryInformationJobObject (
487 JOBOBJECTINFOCLASS JobInformationClass
,
488 PVOID JobInformation
,
489 ULONG JobInformationLength
,
490 PULONG ReturnLength
)
496 PLIST_ENTRY NextEntry
;
497 PKTHREAD CurrentThread
;
498 KPROCESSOR_MODE PreviousMode
;
499 JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimit
;
500 JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION BasicAndIo
;
501 ULONG RequiredLength
, RequiredAlign
, SizeToCopy
, NeededSize
;
505 CurrentThread
= KeGetCurrentThread();
508 if (JobInformationClass
> JobObjectJobSetInformation
|| JobInformationClass
< JobObjectBasicAccountingInformation
)
510 return STATUS_INVALID_INFO_CLASS
;
513 /* Get associated lengths & alignments */
514 RequiredLength
= PspJobInfoLengths
[JobInformationClass
];
515 RequiredAlign
= PspJobInfoAlign
[JobInformationClass
];
516 SizeToCopy
= RequiredLength
;
517 NeededSize
= RequiredLength
;
519 /* If length mismatch (needed versus provided) */
520 if (JobInformationLength
!= RequiredLength
)
522 /* This can only be accepted if: JobObjectBasicProcessIdList or JobObjectSecurityLimitInformation
523 * Or if size is bigger than needed
525 if ((JobInformationClass
!= JobObjectBasicProcessIdList
&& JobInformationClass
!= JobObjectSecurityLimitInformation
) ||
526 JobInformationLength
< RequiredLength
)
528 return STATUS_INFO_LENGTH_MISMATCH
;
531 /* Set what we need to copy out */
532 SizeToCopy
= JobInformationLength
;
535 PreviousMode
= ExGetPreviousMode();
536 /* If not comming from umode, we need to probe buffers */
537 if (PreviousMode
!= KernelMode
)
539 ASSERT(((RequiredAlign
) == 1) || ((RequiredAlign
) == 2) || ((RequiredAlign
) == 4) || ((RequiredAlign
) == 8) || ((RequiredAlign
) == 16));
543 /* Probe out buffer for write */
544 if (JobInformation
!= NULL
)
546 ProbeForWrite(JobInformation
, JobInformationLength
, RequiredAlign
);
549 /* But also return lenght if asked */
550 if (ReturnLength
!= NULL
)
552 ProbeForWrite(JobInformation
, sizeof(ULONG
), sizeof(ULONG
));
555 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
557 _SEH2_YIELD(return _SEH2_GetExceptionCode());
562 /* If a job handle was provided, use it */
563 if (JobHandle
!= NULL
)
565 Status
= ObReferenceObjectByHandle(JobHandle
,
571 if (!NT_SUCCESS(Status
))
576 /* Otherwise, get our current process' job, if any */
579 PEPROCESS CurrentProcess
;
581 CurrentProcess
= (PEPROCESS
)CurrentThread
->ApcState
.Process
;
582 Job
= CurrentProcess
->Job
;
585 return STATUS_ACCESS_DENIED
;
588 ObReferenceObject(Job
);
591 /* By default, assume we'll have to copy data */
594 switch (JobInformationClass
)
597 case JobObjectBasicAccountingInformation
:
598 case JobObjectBasicAndIoAccountingInformation
:
600 RtlZeroMemory(&BasicAndIo
.BasicInfo
, sizeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
));
603 KeEnterGuardedRegionThread(CurrentThread
);
604 ExAcquireResourceSharedLite(&Job
->JobLock
, TRUE
);
606 /* Initialize with job counters */
607 BasicAndIo
.BasicInfo
.TotalUserTime
.QuadPart
= Job
->TotalUserTime
.QuadPart
;
608 BasicAndIo
.BasicInfo
.TotalKernelTime
.QuadPart
= Job
->TotalKernelTime
.QuadPart
;
609 BasicAndIo
.BasicInfo
.ThisPeriodTotalUserTime
.QuadPart
= Job
->ThisPeriodTotalUserTime
.QuadPart
;
610 BasicAndIo
.BasicInfo
.ThisPeriodTotalKernelTime
.QuadPart
= Job
->ThisPeriodTotalKernelTime
.QuadPart
;
611 BasicAndIo
.BasicInfo
.TotalPageFaultCount
= Job
->TotalPageFaultCount
;
612 BasicAndIo
.BasicInfo
.TotalProcesses
= Job
->TotalProcesses
;
613 BasicAndIo
.BasicInfo
.ActiveProcesses
= Job
->ActiveProcesses
;
614 BasicAndIo
.BasicInfo
.TotalTerminatedProcesses
= Job
->TotalTerminatedProcesses
;
616 /* We also set IoInfo, even though we might not return it */
617 BasicAndIo
.IoInfo
.ReadOperationCount
= Job
->ReadOperationCount
;
618 BasicAndIo
.IoInfo
.WriteOperationCount
= Job
->WriteOperationCount
;
619 BasicAndIo
.IoInfo
.OtherOperationCount
= Job
->OtherOperationCount
;
620 BasicAndIo
.IoInfo
.ReadTransferCount
= Job
->ReadTransferCount
;
621 BasicAndIo
.IoInfo
.WriteTransferCount
= Job
->WriteTransferCount
;
622 BasicAndIo
.IoInfo
.OtherTransferCount
= Job
->OtherTransferCount
;
624 /* For every process, sum its counters */
625 for (NextEntry
= Job
->ProcessListHead
.Flink
;
626 NextEntry
!= &Job
->ProcessListHead
;
627 NextEntry
= NextEntry
->Flink
)
631 Process
= CONTAINING_RECORD(NextEntry
, EPROCESS
, JobLinks
);
632 if (!BooleanFlagOn(Process
->JobStatus
, 2))
634 PROCESS_VALUES Values
;
636 KeQueryValuesProcess(&Process
->Pcb
, &Values
);
637 BasicAndIo
.BasicInfo
.TotalUserTime
.QuadPart
+= Values
.TotalUserTime
.QuadPart
;
638 BasicAndIo
.BasicInfo
.TotalKernelTime
.QuadPart
+= Values
.TotalKernelTime
.QuadPart
;
639 BasicAndIo
.IoInfo
.ReadOperationCount
+= Values
.IoInfo
.ReadOperationCount
;
640 BasicAndIo
.IoInfo
.WriteOperationCount
+= Values
.IoInfo
.WriteOperationCount
;
641 BasicAndIo
.IoInfo
.OtherOperationCount
+= Values
.IoInfo
.OtherOperationCount
;
642 BasicAndIo
.IoInfo
.ReadTransferCount
+= Values
.IoInfo
.ReadTransferCount
;
643 BasicAndIo
.IoInfo
.WriteTransferCount
+= Values
.IoInfo
.WriteTransferCount
;
644 BasicAndIo
.IoInfo
.OtherTransferCount
+= Values
.IoInfo
.OtherTransferCount
;
649 ExReleaseResourceLite(&Job
->JobLock
);
650 KeLeaveGuardedRegionThread(CurrentThread
);
652 /* We'll copy back the buffer */
653 GenericCopy
= &BasicAndIo
;
654 Status
= STATUS_SUCCESS
;
658 /* Limits information */
659 case JobObjectBasicLimitInformation
:
660 case JobObjectExtendedLimitInformation
:
662 KeEnterGuardedRegionThread(CurrentThread
);
663 ExAcquireResourceSharedLite(&Job
->JobLock
, TRUE
);
665 /* Copy basic information */
666 ExtendedLimit
.BasicLimitInformation
.LimitFlags
= Job
->LimitFlags
;
667 ExtendedLimit
.BasicLimitInformation
.MinimumWorkingSetSize
= Job
->MinimumWorkingSetSize
;
668 ExtendedLimit
.BasicLimitInformation
.MaximumWorkingSetSize
= Job
->MaximumWorkingSetSize
;
669 ExtendedLimit
.BasicLimitInformation
.ActiveProcessLimit
= Job
->ActiveProcessLimit
;
670 ExtendedLimit
.BasicLimitInformation
.PriorityClass
= Job
->PriorityClass
;
671 ExtendedLimit
.BasicLimitInformation
.SchedulingClass
= Job
->SchedulingClass
;
672 ExtendedLimit
.BasicLimitInformation
.Affinity
= Job
->Affinity
;
673 ExtendedLimit
.BasicLimitInformation
.PerProcessUserTimeLimit
.QuadPart
= Job
->PerProcessUserTimeLimit
.QuadPart
;
674 ExtendedLimit
.BasicLimitInformation
.PerJobUserTimeLimit
.QuadPart
= Job
->PerJobUserTimeLimit
.QuadPart
;
676 /* If asking for extending limits */
677 if (JobInformationClass
== JobObjectExtendedLimitInformation
)
679 /* Lock our memory lock */
680 KeAcquireGuardedMutexUnsafe(&Job
->MemoryLimitsLock
);
682 ExtendedLimit
.ProcessMemoryLimit
= Job
->ProcessMemoryLimit
<< PAGE_SHIFT
;
683 ExtendedLimit
.JobMemoryLimit
= Job
->JobMemoryLimit
<< PAGE_SHIFT
;
684 ExtendedLimit
.PeakProcessMemoryUsed
= Job
->PeakProcessMemoryUsed
<< PAGE_SHIFT
;
685 ExtendedLimit
.PeakJobMemoryUsed
= Job
->PeakJobMemoryUsed
<< PAGE_SHIFT
;
686 KeReleaseGuardedMutexUnsafe(&Job
->MemoryLimitsLock
);
689 ExReleaseResourceLite(&Job
->JobLock
);
690 KeLeaveGuardedRegionThread(CurrentThread
);
692 /* We'll never return IoInfo, so zero it out to avoid
695 RtlZeroMemory(&ExtendedLimit
.IoInfo
, sizeof(IO_COUNTERS
));
700 ExReleaseResourceLite(&Job
->JobLock
);
701 KeLeaveGuardedRegionThread(CurrentThread
);
704 /* We'll copy back the buffer */
705 GenericCopy
= &ExtendedLimit
;
706 Status
= STATUS_SUCCESS
;
711 DPRINT1("Class %d not implemented\n", JobInformationClass
);
712 Status
= STATUS_NOT_IMPLEMENTED
;
716 /* Job is no longer required */
717 ObDereferenceObject(Job
);
719 /* If we succeeed, copy back data */
720 if (NT_SUCCESS(Status
))
724 /* If we have anything to copy, do it */
727 RtlCopyMemory(JobInformation
, GenericCopy
, SizeToCopy
);
730 /* And return length if asked */
731 if (ReturnLength
!= NULL
)
733 *ReturnLength
= NeededSize
;
736 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
738 _SEH2_YIELD(return _SEH2_GetExceptionCode());
752 NtSetInformationJobObject (
754 JOBOBJECTINFOCLASS JobInformationClass
,
755 PVOID JobInformation
,
756 ULONG JobInformationLength
)
760 PKTHREAD CurrentThread
;
761 ACCESS_MASK DesiredAccess
;
762 KPROCESSOR_MODE PreviousMode
;
763 ULONG RequiredLength
, RequiredAlign
;
767 CurrentThread
= KeGetCurrentThread();
770 if (JobInformationClass
> JobObjectJobSetInformation
|| JobInformationClass
< JobObjectBasicAccountingInformation
)
772 return STATUS_INVALID_INFO_CLASS
;
775 /* Get associated lengths & alignments */
776 RequiredLength
= PspJobInfoLengths
[JobInformationClass
];
777 RequiredAlign
= PspJobInfoAlign
[JobInformationClass
];
779 PreviousMode
= ExGetPreviousMode();
780 /* If not comming from umode, we need to probe buffers */
781 if (PreviousMode
!= KernelMode
)
783 ASSERT(((RequiredAlign
) == 1) || ((RequiredAlign
) == 2) || ((RequiredAlign
) == 4) || ((RequiredAlign
) == 8) || ((RequiredAlign
) == 16));
787 /* Probe out buffer for read */
788 if (JobInformationLength
!= 0)
790 ProbeForRead(JobInformation
, JobInformationLength
, RequiredAlign
);
793 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
795 _SEH2_YIELD(return _SEH2_GetExceptionCode());
800 /* Validate input size */
801 if (JobInformationLength
!= RequiredLength
)
803 return STATUS_INFO_LENGTH_MISMATCH
;
806 /* Open the given job */
807 DesiredAccess
= JOB_OBJECT_SET_ATTRIBUTES
;
808 if (JobInformationClass
== JobObjectSecurityLimitInformation
)
810 DesiredAccess
|= JOB_OBJECT_SET_SECURITY_ATTRIBUTES
;
812 Status
= ObReferenceObjectByHandle(JobHandle
,
818 if (!NT_SUCCESS(Status
))
823 /* And set the information */
824 KeEnterGuardedRegionThread(CurrentThread
);
825 switch (JobInformationClass
)
827 case JobObjectExtendedLimitInformation
:
828 DPRINT1("Class JobObjectExtendedLimitInformation not implemented\n");
829 Status
= STATUS_SUCCESS
;
833 DPRINT1("Class %d not implemented\n", JobInformationClass
);
834 Status
= STATUS_NOT_IMPLEMENTED
;
837 KeLeaveGuardedRegionThread(CurrentThread
);
839 ObfDereferenceObject(Job
);
850 NtTerminateJobObject (
852 NTSTATUS ExitStatus
)
854 KPROCESSOR_MODE PreviousMode
;
860 PreviousMode
= ExGetPreviousMode();
862 Status
= ObReferenceObjectByHandle(
864 JOB_OBJECT_TERMINATE
,
869 if(NT_SUCCESS(Status
))
871 Status
= PspTerminateJobObject(
875 ObDereferenceObject(Job
);
887 PsGetJobLock ( PEJOB Job
)
890 return (PVOID
)&Job
->JobLock
;
899 PsGetJobSessionId ( PEJOB Job
)
902 return Job
->SessionId
;
911 PsGetJobUIRestrictionsClass ( PEJOB Job
)
914 return Job
->UIRestrictionsClass
;
923 PsSetJobUIRestrictionsClass(PEJOB Job
,
924 ULONG UIRestrictionsClass
)
927 (void)InterlockedExchangeUL(&Job
->UIRestrictionsClass
, UIRestrictionsClass
);
928 /* FIXME - walk through the job process list and update the restrictions? */