NtOpenThreadToken() opens the thread token if OpenAsSelf is FALSE.
[reactos.git] / reactos / ntoskrnl / ps / create.c
1 /* $Id: create.c,v 1.75 2004/07/13 11:48:32 ekohl Exp $
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ps/thread.c
6 * PURPOSE: Thread managment
7 * PROGRAMMER: David Welch (welch@mcmail.com)
8 * REVISION HISTORY:
9 * 23/06/98: Created
10 * 12/10/99: Phillip Susi: Thread priorities, and APC work
11 * 09/08/03: Skywing: ThreadEventPair support (delete)
12 */
13
14 /*
15 * NOTE:
16 *
17 * All of the routines that manipulate the thread queue synchronize on
18 * a single spinlock
19 *
20 */
21
22 /* INCLUDES ****************************************************************/
23
24 #include <limits.h>
25
26 #define NTOS_MODE_KERNEL
27 #include <ntos.h>
28 #include <internal/ke.h>
29 #include <internal/ob.h>
30 #include <internal/ps.h>
31 #include <internal/se.h>
32 #include <internal/id.h>
33 #include <internal/dbg.h>
34 #include <internal/ldr.h>
35
36 #define NDEBUG
37 #include <internal/debug.h>
38
39 /* GLOBAL *******************************************************************/
40
41 static ULONG PiNextThreadUniqueId = 0;
42
43 extern KSPIN_LOCK PiThreadListLock;
44 extern ULONG PiNrThreads;
45
46 extern LIST_ENTRY PiThreadListHead;
47
48 #define MAX_THREAD_NOTIFY_ROUTINE_COUNT 8
49
50 static ULONG PiThreadNotifyRoutineCount = 0;
51 static PCREATE_THREAD_NOTIFY_ROUTINE
52 PiThreadNotifyRoutine[MAX_THREAD_NOTIFY_ROUTINE_COUNT];
53
54 /* FUNCTIONS ***************************************************************/
55
56 NTSTATUS STDCALL
57 PsAssignImpersonationToken(PETHREAD Thread,
58 HANDLE TokenHandle)
59 {
60 PACCESS_TOKEN Token;
61 SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
62 NTSTATUS Status;
63
64 if (TokenHandle != NULL)
65 {
66 Status = ObReferenceObjectByHandle(TokenHandle,
67 TOKEN_IMPERSONATE,
68 SepTokenObjectType,
69 KeGetPreviousMode(),
70 (PVOID*)&Token,
71 NULL);
72 if (!NT_SUCCESS(Status))
73 {
74 return(Status);
75 }
76 ImpersonationLevel = Token->ImpersonationLevel;
77 }
78 else
79 {
80 Token = NULL;
81 ImpersonationLevel = 0;
82 }
83
84 PsImpersonateClient(Thread,
85 Token,
86 FALSE,
87 FALSE,
88 ImpersonationLevel);
89 if (Token != NULL)
90 {
91 ObDereferenceObject(Token);
92 }
93
94 return(STATUS_SUCCESS);
95 }
96
97
98 /*
99 * @implemented
100 */
101 VOID STDCALL
102 PsRevertToSelf (VOID)
103 {
104 PETHREAD Thread;
105
106 Thread = PsGetCurrentThread ();
107
108 if (Thread->ActiveImpersonationInfo == TRUE)
109 {
110 ObDereferenceObject (Thread->ImpersonationInfo->Token);
111 Thread->ActiveImpersonationInfo = FALSE;
112 }
113 }
114
115
116 /*
117 * @implemented
118 */
119 VOID STDCALL
120 PsImpersonateClient (IN PETHREAD Thread,
121 IN PACCESS_TOKEN Token,
122 IN BOOLEAN CopyOnOpen,
123 IN BOOLEAN EffectiveOnly,
124 IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
125 {
126 if (Token == NULL)
127 {
128 if (Thread->ActiveImpersonationInfo == TRUE)
129 {
130 Thread->ActiveImpersonationInfo = FALSE;
131 if (Thread->ImpersonationInfo->Token != NULL)
132 {
133 ObDereferenceObject (Thread->ImpersonationInfo->Token);
134 }
135 }
136 return;
137 }
138
139 if (Thread->ImpersonationInfo == NULL)
140 {
141 Thread->ImpersonationInfo = ExAllocatePool (NonPagedPool,
142 sizeof(PS_IMPERSONATION_INFO));
143 }
144
145 Thread->ImpersonationInfo->Level = ImpersonationLevel;
146 Thread->ImpersonationInfo->CopyOnOpen = CopyOnOpen;
147 Thread->ImpersonationInfo->EffectiveOnly = EffectiveOnly;
148 Thread->ImpersonationInfo->Token = Token;
149 ObReferenceObjectByPointer (Token,
150 0,
151 SepTokenObjectType,
152 KernelMode);
153 Thread->ActiveImpersonationInfo = TRUE;
154 }
155
156
157 PACCESS_TOKEN
158 PsReferenceEffectiveToken(PETHREAD Thread,
159 PTOKEN_TYPE TokenType,
160 PBOOLEAN EffectiveOnly,
161 PSECURITY_IMPERSONATION_LEVEL Level)
162 {
163 PEPROCESS Process;
164 PACCESS_TOKEN Token;
165
166 if (Thread->ActiveImpersonationInfo == FALSE)
167 {
168 Process = Thread->ThreadsProcess;
169 *TokenType = TokenPrimary;
170 *EffectiveOnly = FALSE;
171 Token = Process->Token;
172 }
173 else
174 {
175 Token = Thread->ImpersonationInfo->Token;
176 *TokenType = TokenImpersonation;
177 *EffectiveOnly = Thread->ImpersonationInfo->EffectiveOnly;
178 *Level = Thread->ImpersonationInfo->Level;
179 }
180 return(Token);
181 }
182
183
184 NTSTATUS STDCALL
185 NtImpersonateThread(IN HANDLE ThreadHandle,
186 IN HANDLE ThreadToImpersonateHandle,
187 IN PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService)
188 {
189 SECURITY_CLIENT_CONTEXT ClientContext;
190 PETHREAD Thread;
191 PETHREAD ThreadToImpersonate;
192 NTSTATUS Status;
193
194 Status = ObReferenceObjectByHandle (ThreadHandle,
195 0,
196 PsThreadType,
197 UserMode,
198 (PVOID*)&Thread,
199 NULL);
200 if (!NT_SUCCESS (Status))
201 {
202 return Status;
203 }
204
205 Status = ObReferenceObjectByHandle (ThreadToImpersonateHandle,
206 0,
207 PsThreadType,
208 UserMode,
209 (PVOID*)&ThreadToImpersonate,
210 NULL);
211 if (!NT_SUCCESS(Status))
212 {
213 ObDereferenceObject (Thread);
214 return Status;
215 }
216
217 Status = SeCreateClientSecurity (ThreadToImpersonate,
218 SecurityQualityOfService,
219 0,
220 &ClientContext);
221 if (!NT_SUCCESS(Status))
222 {
223 ObDereferenceObject (ThreadToImpersonate);
224 ObDereferenceObject (Thread);
225 return Status;
226 }
227
228 SeImpersonateClient (&ClientContext,
229 Thread);
230 if (ClientContext.Token != NULL)
231 {
232 ObDereferenceObject (ClientContext.Token);
233 }
234
235 ObDereferenceObject (ThreadToImpersonate);
236 ObDereferenceObject (Thread);
237
238 return STATUS_SUCCESS;
239 }
240
241
242 NTSTATUS STDCALL
243 NtOpenThreadToken (IN HANDLE ThreadHandle,
244 IN ACCESS_MASK DesiredAccess,
245 IN BOOLEAN OpenAsSelf,
246 OUT PHANDLE TokenHandle)
247 {
248 PACCESS_TOKEN Token;
249 PETHREAD Thread;
250 NTSTATUS Status;
251
252 Status = ObReferenceObjectByHandle (ThreadHandle,
253 0,
254 PsThreadType,
255 UserMode,
256 (PVOID*)&Thread,
257 NULL);
258 if (!NT_SUCCESS(Status))
259 {
260 return(Status);
261 }
262
263 if (OpenAsSelf)
264 {
265 Token = Thread->ThreadsProcess->Token;
266 }
267 else
268 {
269 if (Thread->ActiveImpersonationInfo == FALSE)
270 {
271 ObDereferenceObject (Thread);
272 return STATUS_NO_TOKEN;
273 }
274
275 Token = Thread->ImpersonationInfo->Token;
276 }
277
278 if (Token == NULL)
279 {
280 ObDereferenceObject (Thread);
281 return STATUS_NO_TOKEN;
282 }
283
284 Status = ObCreateHandle (PsGetCurrentProcess(),
285 Token,
286 DesiredAccess,
287 FALSE,
288 TokenHandle);
289
290 ObDereferenceObject (Thread);
291
292 return Status;
293 }
294
295
296 /*
297 * @implemented
298 */
299 PACCESS_TOKEN STDCALL
300 PsReferenceImpersonationToken(IN PETHREAD Thread,
301 OUT PBOOLEAN CopyOnOpen,
302 OUT PBOOLEAN EffectiveOnly,
303 OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
304 {
305 if (Thread->ActiveImpersonationInfo == FALSE)
306 {
307 return NULL;
308 }
309
310 *ImpersonationLevel = Thread->ImpersonationInfo->Level;
311 *CopyOnOpen = Thread->ImpersonationInfo->CopyOnOpen;
312 *EffectiveOnly = Thread->ImpersonationInfo->EffectiveOnly;
313 ObReferenceObjectByPointer (Thread->ImpersonationInfo->Token,
314 TOKEN_ALL_ACCESS,
315 SepTokenObjectType,
316 KernelMode);
317
318 return Thread->ImpersonationInfo->Token;
319 }
320
321
322 VOID
323 PiBeforeBeginThread(CONTEXT c)
324 {
325 KeLowerIrql(PASSIVE_LEVEL);
326 }
327
328
329 VOID STDCALL
330 PiDeleteThread(PVOID ObjectBody)
331 {
332 KIRQL oldIrql;
333 PETHREAD Thread;
334
335 Thread = (PETHREAD)ObjectBody;
336
337 DPRINT("PiDeleteThread(ObjectBody %x)\n",ObjectBody);
338
339 ObDereferenceObject(Thread->ThreadsProcess);
340 Thread->ThreadsProcess = NULL;
341
342 KeAcquireSpinLock(&PiThreadListLock, &oldIrql);
343 PiNrThreads--;
344 RemoveEntryList(&Thread->Tcb.ThreadListEntry);
345 KeReleaseSpinLock(&PiThreadListLock, oldIrql);
346
347 KeReleaseThread(Thread);
348 DPRINT("PiDeleteThread() finished\n");
349 }
350
351
352 NTSTATUS
353 PsInitializeThread(HANDLE ProcessHandle,
354 PETHREAD* ThreadPtr,
355 PHANDLE ThreadHandle,
356 ACCESS_MASK DesiredAccess,
357 POBJECT_ATTRIBUTES ThreadAttributes,
358 BOOLEAN First)
359 {
360 PETHREAD Thread;
361 NTSTATUS Status;
362 KIRQL oldIrql;
363 PEPROCESS Process;
364
365 /*
366 * Reference process
367 */
368 if (ProcessHandle != NULL)
369 {
370 Status = ObReferenceObjectByHandle(ProcessHandle,
371 PROCESS_CREATE_THREAD,
372 PsProcessType,
373 UserMode,
374 (PVOID*)&Process,
375 NULL);
376 if (Status != STATUS_SUCCESS)
377 {
378 DPRINT("Failed at %s:%d\n",__FILE__,__LINE__);
379 return(Status);
380 }
381 DPRINT( "Creating thread in process %x\n", Process );
382 }
383 else
384 {
385 Process = PsInitialSystemProcess;
386 ObReferenceObjectByPointer(Process,
387 PROCESS_CREATE_THREAD,
388 PsProcessType,
389 UserMode);
390 }
391
392 /*
393 * Create and initialize thread
394 */
395 Status = ObCreateObject(UserMode,
396 PsThreadType,
397 ThreadAttributes,
398 UserMode,
399 NULL,
400 sizeof(ETHREAD),
401 0,
402 0,
403 (PVOID*)&Thread);
404 if (!NT_SUCCESS(Status))
405 {
406 return(Status);
407 }
408
409 Status = ObInsertObject ((PVOID)Thread,
410 NULL,
411 DesiredAccess,
412 0,
413 NULL,
414 ThreadHandle);
415 if (!NT_SUCCESS(Status))
416 {
417 ObDereferenceObject (Thread);
418 return Status;
419 }
420
421 DPRINT("Thread = %x\n",Thread);
422
423 PiNrThreads++;
424
425 KeInitializeThread(&Process->Pcb, &Thread->Tcb, First);
426 Thread->ThreadsProcess = Process;
427 /*
428 * FIXME: What lock protects this?
429 */
430 InsertTailList(&Thread->ThreadsProcess->ThreadListHead,
431 &Thread->Tcb.ProcessThreadListEntry);
432 InitializeListHead(&Thread->TerminationPortList);
433 KeInitializeSpinLock(&Thread->ActiveTimerListLock);
434 InitializeListHead(&Thread->IrpList);
435 Thread->Cid.UniqueThread = (HANDLE)InterlockedIncrement(
436 (LONG *)&PiNextThreadUniqueId);
437 Thread->Cid.UniqueProcess = (HANDLE)Thread->ThreadsProcess->UniqueProcessId;
438 Thread->DeadThread = 0;
439 Thread->Win32Thread = 0;
440 DPRINT("Thread->Cid.UniqueThread %d\n",Thread->Cid.UniqueThread);
441
442 *ThreadPtr = Thread;
443
444 KeAcquireSpinLock(&PiThreadListLock, &oldIrql);
445 InsertTailList(&PiThreadListHead, &Thread->Tcb.ThreadListEntry);
446 KeReleaseSpinLock(&PiThreadListLock, oldIrql);
447
448 Thread->Tcb.BasePriority = (CHAR)Thread->ThreadsProcess->Pcb.BasePriority;
449 Thread->Tcb.Priority = Thread->Tcb.BasePriority;
450
451 /*
452 * Local Procedure Call facility (LPC)
453 */
454 KeInitializeSemaphore (& Thread->LpcReplySemaphore, 0, LONG_MAX);
455 Thread->LpcReplyMessage = NULL;
456 Thread->LpcReplyMessageId = 0; /* not valid */
457 /* Thread->LpcReceiveMessageId = 0; */
458 Thread->LpcExitThreadCalled = FALSE;
459 Thread->LpcReceivedMsgIdValid = FALSE;
460
461 return(STATUS_SUCCESS);
462 }
463
464
465 static NTSTATUS
466 PsCreateTeb(HANDLE ProcessHandle,
467 PTEB *TebPtr,
468 PETHREAD Thread,
469 PUSER_STACK UserStack)
470 {
471 MEMORY_BASIC_INFORMATION Info;
472 NTSTATUS Status;
473 ULONG ByteCount;
474 ULONG RegionSize;
475 ULONG TebSize;
476 PVOID TebBase;
477 TEB Teb;
478 ULONG ResultLength;
479
480 TebBase = (PVOID)0x7FFDE000;
481 TebSize = PAGE_SIZE;
482
483 while (TRUE)
484 {
485 Status = ZwQueryVirtualMemory(ProcessHandle,
486 TebBase,
487 MemoryBasicInformation,
488 &Info,
489 sizeof(Info),
490 &ResultLength);
491 if (!NT_SUCCESS(Status))
492 {
493 CPRINT("ZwQueryVirtualMemory (Status %x)\n", Status);
494 KEBUGCHECK(0);
495 }
496 /* FIXME: Race between this and the above check */
497 if (Info.State == MEM_FREE)
498 {
499 /* The TEB must reside in user space */
500 Status = ZwAllocateVirtualMemory(ProcessHandle,
501 &TebBase,
502 0,
503 &TebSize,
504 MEM_RESERVE | MEM_COMMIT,
505 PAGE_READWRITE);
506 if (NT_SUCCESS(Status))
507 {
508 break;
509 }
510 }
511
512 TebBase = (char*)TebBase - TebSize;
513 }
514
515 DPRINT ("TebBase %p TebSize %lu\n", TebBase, TebSize);
516
517 RtlZeroMemory(&Teb, sizeof(TEB));
518 /* set all pointers to and from the TEB */
519 Teb.Tib.Self = TebBase;
520 if (Thread->ThreadsProcess)
521 {
522 Teb.Peb = Thread->ThreadsProcess->Peb; /* No PEB yet!! */
523 }
524 DPRINT("Teb.Peb %x\n", Teb.Peb);
525
526 /* store stack information from UserStack */
527 if(UserStack != NULL)
528 {
529 /* fixed-size stack */
530 if(UserStack->FixedStackBase && UserStack->FixedStackLimit)
531 {
532 Teb.Tib.StackBase = UserStack->FixedStackBase;
533 Teb.Tib.StackLimit = UserStack->FixedStackLimit;
534 Teb.DeallocationStack = UserStack->FixedStackLimit;
535 }
536 /* expandable stack */
537 else
538 {
539 Teb.Tib.StackBase = UserStack->ExpandableStackBase;
540 Teb.Tib.StackLimit = UserStack->ExpandableStackLimit;
541 Teb.DeallocationStack = UserStack->ExpandableStackBottom;
542 }
543 }
544
545 /* more initialization */
546 Teb.Cid.UniqueThread = Thread->Cid.UniqueThread;
547 Teb.Cid.UniqueProcess = Thread->Cid.UniqueProcess;
548 Teb.CurrentLocale = PsDefaultThreadLocaleId;
549
550 /* Terminate the exception handler list */
551 Teb.Tib.ExceptionList = (PVOID)-1;
552
553 DPRINT("sizeof(TEB) %x\n", sizeof(TEB));
554
555 /* write TEB data into teb page */
556 Status = NtWriteVirtualMemory(ProcessHandle,
557 TebBase,
558 &Teb,
559 sizeof(TEB),
560 &ByteCount);
561
562 if (!NT_SUCCESS(Status))
563 {
564 /* free TEB */
565 DPRINT1 ("Writing TEB failed!\n");
566
567 RegionSize = 0;
568 NtFreeVirtualMemory(ProcessHandle,
569 TebBase,
570 &RegionSize,
571 MEM_RELEASE);
572
573 return Status;
574 }
575
576 if (TebPtr != NULL)
577 {
578 *TebPtr = (PTEB)TebBase;
579 }
580
581 DPRINT("TEB allocated at %p\n", TebBase);
582
583 return Status;
584 }
585
586
587 VOID STDCALL
588 LdrInitApcRundownRoutine(PKAPC Apc)
589 {
590 ExFreePool(Apc);
591 }
592
593
594 VOID STDCALL
595 LdrInitApcKernelRoutine(PKAPC Apc,
596 PKNORMAL_ROUTINE* NormalRoutine,
597 PVOID* NormalContext,
598 PVOID* SystemArgument1,
599 PVOID* SystemArgument2)
600 {
601 ExFreePool(Apc);
602 }
603
604
605 NTSTATUS STDCALL
606 NtCreateThread(PHANDLE ThreadHandle,
607 ACCESS_MASK DesiredAccess,
608 POBJECT_ATTRIBUTES ObjectAttributes,
609 HANDLE ProcessHandle,
610 PCLIENT_ID Client,
611 PCONTEXT ThreadContext,
612 PUSER_STACK UserStack,
613 BOOLEAN CreateSuspended)
614 {
615 PETHREAD Thread;
616 PTEB TebBase;
617 NTSTATUS Status;
618 PKAPC LdrInitApc;
619
620 DPRINT("NtCreateThread(ThreadHandle %x, PCONTEXT %x)\n",
621 ThreadHandle,ThreadContext);
622
623 Status = PsInitializeThread(ProcessHandle,
624 &Thread,
625 ThreadHandle,
626 DesiredAccess,
627 ObjectAttributes,
628 FALSE);
629 if (!NT_SUCCESS(Status))
630 {
631 return(Status);
632 }
633
634 Status = KiArchInitThreadWithContext(&Thread->Tcb, ThreadContext);
635 if (!NT_SUCCESS(Status))
636 {
637 return(Status);
638 }
639
640 Status = PsCreateTeb(ProcessHandle,
641 &TebBase,
642 Thread,
643 UserStack);
644 if (!NT_SUCCESS(Status))
645 {
646 return(Status);
647 }
648 Thread->Tcb.Teb = TebBase;
649
650 Thread->StartAddress = NULL;
651
652 if (Client != NULL)
653 {
654 *Client = Thread->Cid;
655 }
656
657 /*
658 * Maybe send a message to the process's debugger
659 */
660 DbgkCreateThread((PVOID)ThreadContext->Eip);
661
662 /*
663 * First, force the thread to be non-alertable for user-mode alerts.
664 */
665 Thread->Tcb.Alertable = FALSE;
666
667 /*
668 * If the thread is to be created suspended then queue an APC to
669 * do the suspend before we run any userspace code.
670 */
671 if (CreateSuspended)
672 {
673 PsSuspendThread(Thread, NULL);
674 }
675
676 /*
677 * Queue an APC to the thread that will execute the ntdll startup
678 * routine.
679 */
680 LdrInitApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
681 KeInitializeApc(LdrInitApc, &Thread->Tcb, OriginalApcEnvironment, LdrInitApcKernelRoutine,
682 LdrInitApcRundownRoutine, LdrpGetSystemDllEntryPoint(),
683 UserMode, NULL);
684 KeInsertQueueApc(LdrInitApc, NULL, NULL, IO_NO_INCREMENT);
685
686 /*
687 * Start the thread running and force it to execute the APC(s) we just
688 * queued before it runs anything else in user-mode.
689 */
690 Thread->Tcb.Alertable = TRUE;
691 Thread->Tcb.Alerted[0] = 1;
692 PsUnblockThread(Thread, NULL);
693
694 return(STATUS_SUCCESS);
695 }
696
697
698 /*
699 * @implemented
700 */
701 NTSTATUS STDCALL
702 PsCreateSystemThread(PHANDLE ThreadHandle,
703 ACCESS_MASK DesiredAccess,
704 POBJECT_ATTRIBUTES ObjectAttributes,
705 HANDLE ProcessHandle,
706 PCLIENT_ID ClientId,
707 PKSTART_ROUTINE StartRoutine,
708 PVOID StartContext)
709 /*
710 * FUNCTION: Creates a thread which executes in kernel mode
711 * ARGUMENTS:
712 * ThreadHandle (OUT) = Caller supplied storage for the returned thread
713 * handle
714 * DesiredAccess = Requested access to the thread
715 * ObjectAttributes = Object attributes (optional)
716 * ProcessHandle = Handle of process thread will run in
717 * NULL to use system process
718 * ClientId (OUT) = Caller supplied storage for the returned client id
719 * of the thread (optional)
720 * StartRoutine = Entry point for the thread
721 * StartContext = Argument supplied to the thread when it begins
722 * execution
723 * RETURNS: Success or failure status
724 */
725 {
726 PETHREAD Thread;
727 NTSTATUS Status;
728
729 DPRINT("PsCreateSystemThread(ThreadHandle %x, ProcessHandle %x)\n",
730 ThreadHandle,ProcessHandle);
731
732 Status = PsInitializeThread(ProcessHandle,
733 &Thread,
734 ThreadHandle,
735 DesiredAccess,
736 ObjectAttributes,
737 FALSE);
738 if (!NT_SUCCESS(Status))
739 {
740 return(Status);
741 }
742
743 Thread->StartAddress = StartRoutine;
744 Status = KiArchInitThread(&Thread->Tcb, StartRoutine, StartContext);
745 if (!NT_SUCCESS(Status))
746 {
747 return(Status);
748 }
749
750 if (ClientId != NULL)
751 {
752 *ClientId=Thread->Cid;
753 }
754
755 PsUnblockThread(Thread, NULL);
756
757 return(STATUS_SUCCESS);
758 }
759
760
761 VOID STDCALL
762 PspRunCreateThreadNotifyRoutines(PETHREAD CurrentThread,
763 BOOLEAN Create)
764 {
765 ULONG i;
766 CLIENT_ID Cid = CurrentThread->Cid;
767
768 for (i = 0; i < PiThreadNotifyRoutineCount; i++)
769 {
770 PiThreadNotifyRoutine[i](Cid.UniqueProcess, Cid.UniqueThread, Create);
771 }
772 }
773
774
775 /*
776 * @implemented
777 */
778 NTSTATUS STDCALL
779 PsSetCreateThreadNotifyRoutine(IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine)
780 {
781 if (PiThreadNotifyRoutineCount >= MAX_THREAD_NOTIFY_ROUTINE_COUNT)
782 {
783 return(STATUS_INSUFFICIENT_RESOURCES);
784 }
785
786 PiThreadNotifyRoutine[PiThreadNotifyRoutineCount] = NotifyRoutine;
787 PiThreadNotifyRoutineCount++;
788
789 return(STATUS_SUCCESS);
790 }
791
792 /* EOF */