Implement A->W call for GetNamedSecurityInfo.
[wine] / dlls / kernel / thread.c
1 /*
2  * Win32 threads
3  *
4  * Copyright 1996 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <fcntl.h>
26 #include <stdarg.h>
27 #include <signal.h>
28 #include <sys/types.h>
29 #ifdef HAVE_SYS_TIMES_H
30 #include <sys/times.h>
31 #endif
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35
36 #include "ntstatus.h"
37 #include "windef.h"
38 #include "winbase.h"
39 #include "winerror.h"
40 #include "winnls.h"
41 #include "module.h"
42 #include "thread.h"
43 #include "wine/winbase16.h"
44 #include "wine/exception.h"
45 #include "wine/library.h"
46 #include "wine/pthread.h"
47 #include "wine/server.h"
48 #include "wine/debug.h"
49
50 WINE_DEFAULT_DEBUG_CHANNEL(thread);
51 WINE_DECLARE_DEBUG_CHANNEL(relay);
52
53
54 /***********************************************************************
55  *           THREAD_InitStack
56  *
57  * Allocate the stack of a thread.
58  */
59 TEB *THREAD_InitStack( TEB *teb, DWORD stack_size )
60 {
61     DWORD old_prot;
62     DWORD page_size = getpagesize();
63     void *base;
64
65     stack_size = (stack_size + (page_size - 1)) & ~(page_size - 1);
66     if (stack_size < 1024 * 1024) stack_size = 1024 * 1024;  /* Xlib needs a large stack */
67
68     if (!(base = VirtualAlloc( NULL, stack_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE )))
69         return NULL;
70
71     teb->DeallocationStack = base;
72     teb->Tib.StackBase     = (char *)base + stack_size;
73     teb->Tib.StackLimit    = base;  /* note: limit is lower than base since the stack grows down */
74
75     /* Setup guard pages */
76
77     VirtualProtect( base, 1, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &old_prot );
78     return teb;
79 }
80
81
82 struct new_thread_info
83 {
84     LPTHREAD_START_ROUTINE func;
85     void                  *arg;
86 };
87
88 /***********************************************************************
89  *           THREAD_Start
90  *
91  * Start execution of a newly created thread. Does not return.
92  */
93 static void CALLBACK THREAD_Start( void *ptr )
94 {
95     struct new_thread_info *info = ptr;
96     LPTHREAD_START_ROUTINE func = info->func;
97     void *arg = info->arg;
98
99     RtlFreeHeap( GetProcessHeap(), 0, info );
100
101     if (TRACE_ON(relay))
102         DPRINTF("%04lx:Starting thread (entryproc=%p)\n", GetCurrentThreadId(), func );
103
104     __TRY
105     {
106         MODULE_DllThreadAttach( NULL );
107         ExitThread( func( arg ) );
108     }
109     __EXCEPT(UnhandledExceptionFilter)
110     {
111         TerminateThread( GetCurrentThread(), GetExceptionCode() );
112     }
113     __ENDTRY
114 }
115
116
117 /***********************************************************************
118  *           CreateThread   (KERNEL32.@)
119  */
120 HANDLE WINAPI CreateThread( SECURITY_ATTRIBUTES *sa, SIZE_T stack,
121                             LPTHREAD_START_ROUTINE start, LPVOID param,
122                             DWORD flags, LPDWORD id )
123 {
124     HANDLE handle;
125     CLIENT_ID client_id;
126     NTSTATUS status;
127     SIZE_T stack_reserve = 0, stack_commit = 0;
128     struct new_thread_info *info;
129
130     if (!(info = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*info) )))
131     {
132         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
133         return 0;
134     }
135     info->func = start;
136     info->arg  = param;
137
138     if (flags & STACK_SIZE_PARAM_IS_A_RESERVATION) stack_reserve = stack;
139     else stack_commit = stack;
140
141     status = RtlCreateUserThread( GetCurrentProcess(), NULL, (flags & CREATE_SUSPENDED) != 0,
142                                   NULL, stack_reserve, stack_commit,
143                                   THREAD_Start, info, &handle, &client_id );
144     if (status == STATUS_SUCCESS)
145     {
146         if (id) *id = (DWORD)client_id.UniqueThread;
147         if (sa && (sa->nLength >= sizeof(*sa)) && sa->bInheritHandle)
148             SetHandleInformation( handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT );
149     }
150     else
151     {
152         RtlFreeHeap( GetProcessHeap(), 0, info );
153         SetLastError( RtlNtStatusToDosError(status) );
154         handle = 0;
155     }
156     return handle;
157 }
158
159
160 /***************************************************************************
161  *                  CreateRemoteThread   (KERNEL32.@)
162  *
163  * Creates a thread that runs in the address space of another process
164  *
165  * PARAMS
166  *
167  * RETURNS
168  *   Success: Handle to the new thread.
169  *   Failure: NULL. Use GetLastError() to find the error cause.
170  *
171  * BUGS
172  *   Unimplemented
173  */
174 HANDLE WINAPI CreateRemoteThread( HANDLE hProcess, SECURITY_ATTRIBUTES *sa, SIZE_T stack,
175                                   LPTHREAD_START_ROUTINE start, LPVOID param,
176                                   DWORD flags, LPDWORD id )
177 {
178     FIXME("(): stub, Write Me.\n");
179
180     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
181     return NULL;
182 }
183
184
185 /***********************************************************************
186  * OpenThread  [KERNEL32.@]   Retrieves a handle to a thread from its thread id
187  */
188 HANDLE WINAPI OpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId )
189 {
190     HANDLE ret = 0;
191     SERVER_START_REQ( open_thread )
192     {
193         req->tid     = dwThreadId;
194         req->access  = dwDesiredAccess;
195         req->inherit = bInheritHandle;
196         if (!wine_server_call_err( req )) ret = reply->handle;
197     }
198     SERVER_END_REQ;
199     return ret;
200 }
201
202
203 /***********************************************************************
204  * ExitThread [KERNEL32.@]  Ends a thread
205  *
206  * RETURNS
207  *    None
208  */
209 void WINAPI ExitThread( DWORD code ) /* [in] Exit code for this thread */
210 {
211     BOOL last;
212     SERVER_START_REQ( terminate_thread )
213     {
214         /* send the exit code to the server */
215         req->handle    = GetCurrentThread();
216         req->exit_code = code;
217         wine_server_call( req );
218         last = reply->last;
219     }
220     SERVER_END_REQ;
221
222     if (last)
223     {
224         LdrShutdownProcess();
225         exit( code );
226     }
227     else
228     {
229         struct wine_pthread_thread_info info;
230         sigset_t block_set;
231         ULONG size;
232
233         LdrShutdownThread();
234         RtlAcquirePebLock();
235         RemoveEntryList( &NtCurrentTeb()->TlsLinks );
236         RtlReleasePebLock();
237
238         info.stack_base  = NtCurrentTeb()->DeallocationStack;
239         info.teb_base    = NtCurrentTeb();
240         info.teb_sel     = wine_get_fs();
241         info.exit_status = code;
242
243         size = 0;
244         NtFreeVirtualMemory( GetCurrentProcess(), &info.stack_base, &size, MEM_RELEASE | MEM_SYSTEM );
245         info.stack_size = size;
246
247         size = 0;
248         NtFreeVirtualMemory( GetCurrentProcess(), &info.teb_base, &size, MEM_RELEASE | MEM_SYSTEM );
249         info.teb_size = size;
250
251         /* block the async signals */
252         sigemptyset( &block_set );
253         sigaddset( &block_set, SIGALRM );
254         sigaddset( &block_set, SIGIO );
255         sigaddset( &block_set, SIGINT );
256         sigaddset( &block_set, SIGHUP );
257         sigaddset( &block_set, SIGUSR1 );
258         sigaddset( &block_set, SIGUSR2 );
259         sigaddset( &block_set, SIGTERM );
260         sigprocmask( SIG_BLOCK, &block_set, NULL );
261
262         close( NtCurrentTeb()->wait_fd[0] );
263         close( NtCurrentTeb()->wait_fd[1] );
264         close( NtCurrentTeb()->reply_fd );
265         close( NtCurrentTeb()->request_fd );
266
267         wine_pthread_exit_thread( &info );
268     }
269 }
270
271
272 /**********************************************************************
273  * TerminateThread [KERNEL32.@]  Terminates a thread
274  *
275  * RETURNS
276  *    Success: TRUE
277  *    Failure: FALSE
278  */
279 BOOL WINAPI TerminateThread( HANDLE handle,    /* [in] Handle to thread */
280                              DWORD exit_code)  /* [in] Exit code for thread */
281 {
282     NTSTATUS status = NtTerminateThread( handle, exit_code );
283     if (status) SetLastError( RtlNtStatusToDosError(status) );
284     return !status;
285 }
286
287
288 /***********************************************************************
289  *           FreeLibraryAndExitThread (KERNEL32.@)
290  */
291 void WINAPI FreeLibraryAndExitThread(HINSTANCE hLibModule, DWORD dwExitCode)
292 {
293     FreeLibrary(hLibModule);
294     ExitThread(dwExitCode);
295 }
296
297
298 /**********************************************************************
299  *              GetExitCodeThread (KERNEL32.@)
300  *
301  * Gets termination status of thread.
302  *
303  * RETURNS
304  *    Success: TRUE
305  *    Failure: FALSE
306  */
307 BOOL WINAPI GetExitCodeThread(
308     HANDLE hthread, /* [in]  Handle to thread */
309     LPDWORD exitcode) /* [out] Address to receive termination status */
310 {
311     THREAD_BASIC_INFORMATION info;
312     NTSTATUS status = NtQueryInformationThread( hthread, ThreadBasicInformation,
313                                                 &info, sizeof(info), NULL );
314
315     if (status)
316     {
317         SetLastError( RtlNtStatusToDosError(status) );
318         return FALSE;
319     }
320     if (exitcode) *exitcode = info.ExitStatus;
321     return TRUE;
322 }
323
324
325 /***********************************************************************
326  * SetThreadContext [KERNEL32.@]  Sets context of thread.
327  *
328  * RETURNS
329  *    Success: TRUE
330  *    Failure: FALSE
331  */
332 BOOL WINAPI SetThreadContext( HANDLE handle,           /* [in]  Handle to thread with context */
333                               const CONTEXT *context ) /* [in] Address of context structure */
334 {
335     NTSTATUS status = NtSetContextThread( handle, context );
336     if (status) SetLastError( RtlNtStatusToDosError(status) );
337     return !status;
338 }
339
340
341 /***********************************************************************
342  * GetThreadContext [KERNEL32.@]  Retrieves context of thread.
343  *
344  * RETURNS
345  *    Success: TRUE
346  *    Failure: FALSE
347  */
348 BOOL WINAPI GetThreadContext( HANDLE handle,     /* [in]  Handle to thread with context */
349                               CONTEXT *context ) /* [out] Address of context structure */
350 {
351     NTSTATUS status = NtGetContextThread( handle, context );
352     if (status) SetLastError( RtlNtStatusToDosError(status) );
353     return !status;
354 }
355
356
357 /**********************************************************************
358  * SuspendThread [KERNEL32.@]  Suspends a thread.
359  *
360  * RETURNS
361  *    Success: Previous suspend count
362  *    Failure: 0xFFFFFFFF
363  */
364 DWORD WINAPI SuspendThread( HANDLE hthread ) /* [in] Handle to the thread */
365 {
366     DWORD ret;
367     NTSTATUS status = NtSuspendThread( hthread, &ret );
368
369     if (status)
370     {
371         ret = ~0U;
372         SetLastError( RtlNtStatusToDosError(status) );
373     }
374     return ret;
375 }
376
377
378 /**********************************************************************
379  * ResumeThread [KERNEL32.@]  Resumes a thread.
380  *
381  * Decrements a thread's suspend count.  When count is zero, the
382  * execution of the thread is resumed.
383  *
384  * RETURNS
385  *    Success: Previous suspend count
386  *    Failure: 0xFFFFFFFF
387  *    Already running: 0
388  */
389 DWORD WINAPI ResumeThread( HANDLE hthread ) /* [in] Identifies thread to restart */
390 {
391     DWORD ret;
392     NTSTATUS status = NtResumeThread( hthread, &ret );
393
394     if (status)
395     {
396         ret = ~0U;
397         SetLastError( RtlNtStatusToDosError(status) );
398     }
399     return ret;
400 }
401
402
403 /**********************************************************************
404  * GetThreadPriority [KERNEL32.@]  Returns priority for thread.
405  *
406  * RETURNS
407  *    Success: Thread's priority level.
408  *    Failure: THREAD_PRIORITY_ERROR_RETURN
409  */
410 INT WINAPI GetThreadPriority(
411     HANDLE hthread) /* [in] Handle to thread */
412 {
413     THREAD_BASIC_INFORMATION info;
414     NTSTATUS status = NtQueryInformationThread( hthread, ThreadBasicInformation,
415                                                 &info, sizeof(info), NULL );
416
417     if (status)
418     {
419         SetLastError( RtlNtStatusToDosError(status) );
420         return THREAD_PRIORITY_ERROR_RETURN;
421     }
422     return info.Priority;
423 }
424
425
426 /**********************************************************************
427  * SetThreadPriority [KERNEL32.@]  Sets priority for thread.
428  *
429  * RETURNS
430  *    Success: TRUE
431  *    Failure: FALSE
432  */
433 BOOL WINAPI SetThreadPriority(
434     HANDLE hthread, /* [in] Handle to thread */
435     INT priority)   /* [in] Thread priority level */
436 {
437     BOOL ret;
438     SERVER_START_REQ( set_thread_info )
439     {
440         req->handle   = hthread;
441         req->priority = priority;
442         req->mask     = SET_THREAD_INFO_PRIORITY;
443         ret = !wine_server_call_err( req );
444     }
445     SERVER_END_REQ;
446     return ret;
447 }
448
449
450 /**********************************************************************
451  * GetThreadPriorityBoost [KERNEL32.@]  Returns priority boost for thread.
452  *
453  * Always reports that priority boost is disabled.
454  *
455  * RETURNS
456  *    Success: TRUE.
457  *    Failure: FALSE
458  */
459 BOOL WINAPI GetThreadPriorityBoost(
460     HANDLE hthread, /* [in] Handle to thread */
461     PBOOL pstate)   /* [out] pointer to var that receives the boost state */
462 {
463     if (pstate) *pstate = FALSE;
464     return NO_ERROR;
465 }
466
467
468 /**********************************************************************
469  * SetThreadPriorityBoost [KERNEL32.@]  Sets priority boost for thread.
470  *
471  * Priority boost is not implemented. Thsi function always returns
472  * FALSE and sets last error to ERROR_CALL_NOT_IMPLEMENTED
473  *
474  * RETURNS
475  *    Always returns FALSE to indicate a failure
476  */
477 BOOL WINAPI SetThreadPriorityBoost(
478     HANDLE hthread, /* [in] Handle to thread */
479     BOOL disable)   /* [in] TRUE to disable priority boost */
480 {
481     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
482     return FALSE;
483 }
484
485
486 /**********************************************************************
487  *           SetThreadAffinityMask   (KERNEL32.@)
488  */
489 DWORD WINAPI SetThreadAffinityMask( HANDLE hThread, DWORD dwThreadAffinityMask )
490 {
491     DWORD ret;
492     SERVER_START_REQ( set_thread_info )
493     {
494         req->handle   = hThread;
495         req->affinity = dwThreadAffinityMask;
496         req->mask     = SET_THREAD_INFO_AFFINITY;
497         ret = !wine_server_call_err( req );
498         /* FIXME: should return previous value */
499     }
500     SERVER_END_REQ;
501     return ret;
502 }
503
504
505 /**********************************************************************
506  * SetThreadIdealProcessor [KERNEL32.@]  Obtains timing information.
507  *
508  * RETURNS
509  *    Success: Value of last call to SetThreadIdealProcessor
510  *    Failure: -1
511  */
512 DWORD WINAPI SetThreadIdealProcessor(
513     HANDLE hThread,          /* [in] Specifies the thread of interest */
514     DWORD dwIdealProcessor)  /* [in] Specifies the new preferred processor */
515 {
516     FIXME("(%p): stub\n",hThread);
517     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
518     return -1L;
519 }
520
521
522 /* callback for QueueUserAPC */
523 static void CALLBACK call_user_apc( ULONG_PTR arg1, ULONG_PTR arg2, ULONG_PTR arg3 )
524 {
525     PAPCFUNC func = (PAPCFUNC)arg1;
526     func( arg2 );
527 }
528
529 /***********************************************************************
530  *              QueueUserAPC  (KERNEL32.@)
531  */
532 DWORD WINAPI QueueUserAPC( PAPCFUNC func, HANDLE hthread, ULONG_PTR data )
533 {
534     NTSTATUS status = NtQueueApcThread( hthread, call_user_apc, (ULONG_PTR)func, data, 0 );
535
536     if (status) SetLastError( RtlNtStatusToDosError(status) );
537     return !status;
538 }
539
540
541 /**********************************************************************
542  * GetThreadTimes [KERNEL32.@]  Obtains timing information.
543  *
544  * RETURNS
545  *    Success: TRUE
546  *    Failure: FALSE
547  */
548 BOOL WINAPI GetThreadTimes(
549     HANDLE thread,         /* [in]  Specifies the thread of interest */
550     LPFILETIME creationtime, /* [out] When the thread was created */
551     LPFILETIME exittime,     /* [out] When the thread was destroyed */
552     LPFILETIME kerneltime,   /* [out] Time thread spent in kernel mode */
553     LPFILETIME usertime)     /* [out] Time thread spent in user mode */
554 {
555     BOOL ret = TRUE;
556
557     if (creationtime || exittime)
558     {
559         /* We need to do a server call to get the creation time or exit time */
560         /* This works on any thread */
561
562         SERVER_START_REQ( get_thread_info )
563         {
564             req->handle = thread;
565             req->tid_in = 0;
566             if ((ret = !wine_server_call_err( req )))
567             {
568                 if (creationtime)
569                     RtlSecondsSince1970ToTime( reply->creation_time, (LARGE_INTEGER*)creationtime );
570                 if (exittime)
571                     RtlSecondsSince1970ToTime( reply->exit_time, (LARGE_INTEGER*)exittime );
572             }
573         }
574         SERVER_END_REQ;
575     }
576     if (ret && (kerneltime || usertime))
577     {
578         /* We call times(2) for kernel time or user time */
579         /* We can only (portably) do this for the current thread */
580         if (thread == GetCurrentThread())
581         {
582             ULONGLONG time;
583             struct tms time_buf;
584             long clocks_per_sec = sysconf(_SC_CLK_TCK);
585
586             times(&time_buf);
587             if (kerneltime)
588             {
589                 time = (ULONGLONG)time_buf.tms_stime * 10000000 / clocks_per_sec;
590                 kerneltime->dwHighDateTime = time >> 32;
591                 kerneltime->dwLowDateTime = (DWORD)time;
592             }
593             if (usertime)
594             {
595                 time = (ULONGLONG)time_buf.tms_utime * 10000000 / clocks_per_sec;
596                 usertime->dwHighDateTime = time >> 32;
597                 usertime->dwLowDateTime = (DWORD)time;
598             }
599         }
600         else
601         {
602             if (kerneltime) kerneltime->dwHighDateTime = kerneltime->dwLowDateTime = 0;
603             if (usertime) usertime->dwHighDateTime = usertime->dwLowDateTime = 0;
604             FIXME("Cannot get kerneltime or usertime of other threads\n");
605         }
606     }
607     return ret;
608 }
609
610
611 /**********************************************************************
612  * VWin32_BoostThreadGroup [KERNEL.535]
613  */
614 VOID WINAPI VWin32_BoostThreadGroup( DWORD threadId, INT boost )
615 {
616     FIXME("(0x%08lx,%d): stub\n", threadId, boost);
617 }
618
619
620 /**********************************************************************
621  * VWin32_BoostThreadStatic [KERNEL.536]
622  */
623 VOID WINAPI VWin32_BoostThreadStatic( DWORD threadId, INT boost )
624 {
625     FIXME("(0x%08lx,%d): stub\n", threadId, boost);
626 }
627
628
629 /***********************************************************************
630  * GetCurrentThread [KERNEL32.@]  Gets pseudohandle for current thread
631  *
632  * RETURNS
633  *    Pseudohandle for the current thread
634  */
635 #undef GetCurrentThread
636 HANDLE WINAPI GetCurrentThread(void)
637 {
638     return (HANDLE)0xfffffffe;
639 }
640
641
642 #ifdef __i386__
643
644 /***********************************************************************
645  *              SetLastError (KERNEL.147)
646  *              SetLastError (KERNEL32.@)
647  */
648 /* void WINAPI SetLastError( DWORD error ); */
649 __ASM_GLOBAL_FUNC( SetLastError,
650                    "movl 4(%esp),%eax\n\t"
651                    ".byte 0x64\n\t"
652                    "movl %eax,0x34\n\t"
653                    "ret $4" )
654
655 /***********************************************************************
656  *              GetLastError (KERNEL.148)
657  *              GetLastError (KERNEL32.@)
658  */
659 /* DWORD WINAPI GetLastError(void); */
660 __ASM_GLOBAL_FUNC( GetLastError, ".byte 0x64\n\tmovl 0x34,%eax\n\tret" )
661
662 /***********************************************************************
663  *              GetCurrentProcessId (KERNEL.471)
664  *              GetCurrentProcessId (KERNEL32.@)
665  */
666 /* DWORD WINAPI GetCurrentProcessId(void) */
667 __ASM_GLOBAL_FUNC( GetCurrentProcessId, ".byte 0x64\n\tmovl 0x20,%eax\n\tret" )
668
669 /***********************************************************************
670  *              GetCurrentThreadId (KERNEL.462)
671  *              GetCurrentThreadId (KERNEL32.@)
672  */
673 /* DWORD WINAPI GetCurrentThreadId(void) */
674 __ASM_GLOBAL_FUNC( GetCurrentThreadId, ".byte 0x64\n\tmovl 0x24,%eax\n\tret" )
675
676 #else  /* __i386__ */
677
678 /**********************************************************************
679  *              SetLastError (KERNEL.147)
680  *              SetLastError (KERNEL32.@)
681  *
682  * Sets the last-error code.
683  */
684 void WINAPI SetLastError( DWORD error ) /* [in] Per-thread error code */
685 {
686     NtCurrentTeb()->LastErrorValue = error;
687 }
688
689 /**********************************************************************
690  *              GetLastError (KERNEL.148)
691  *              GetLastError (KERNEL32.@)
692  *
693  * Returns last-error code.
694  */
695 DWORD WINAPI GetLastError(void)
696 {
697     return NtCurrentTeb()->LastErrorValue;
698 }
699
700 /***********************************************************************
701  *              GetCurrentProcessId (KERNEL.471)
702  *              GetCurrentProcessId (KERNEL32.@)
703  *
704  * Returns process identifier.
705  */
706 DWORD WINAPI GetCurrentProcessId(void)
707 {
708     return (DWORD)NtCurrentTeb()->ClientId.UniqueProcess;
709 }
710
711 /***********************************************************************
712  *              GetCurrentThreadId (KERNEL.462)
713  *              GetCurrentThreadId (KERNEL32.@)
714  *
715  * Returns thread identifier.
716  */
717 DWORD WINAPI GetCurrentThreadId(void)
718 {
719     return (DWORD)NtCurrentTeb()->ClientId.UniqueThread;
720 }
721
722 #endif  /* __i386__ */