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