| /* Linuxthreads - a simple clone()-based implementation of Posix        */ | 
 | /* threads for Linux.                                                   */ | 
 | /* Copyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr)              */ | 
 | /*                                                                      */ | 
 | /* This program is free software; you can redistribute it and/or        */ | 
 | /* modify it under the terms of the GNU Library General Public License  */ | 
 | /* as published by the Free Software Foundation; either version 2       */ | 
 | /* of the License, or (at your option) any later version.               */ | 
 | /*                                                                      */ | 
 | /* This program is distributed in the hope that it will be useful,      */ | 
 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of       */ | 
 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */ | 
 | /* GNU Library General Public License for more details.                 */ | 
 |  | 
 | /* Thread cancellation */ | 
 |  | 
 | #include <errno.h> | 
 | #include <libc-internal.h> | 
 | #include "pthread.h" | 
 | #include "internals.h" | 
 | #include "spinlock.h" | 
 | #include "restart.h" | 
 |  | 
 | #ifdef _STACK_GROWS_DOWN | 
 | # define FRAME_LEFT(frame, other) ((char *) frame >= (char *) other) | 
 | #elif defined _STACK_GROWS_UP | 
 | # define FRAME_LEFT(frame, other) ((char *) frame <= (char *) other) | 
 | #else | 
 | # error "Define either _STACK_GROWS_DOWN or _STACK_GROWS_UP" | 
 | #endif | 
 |  | 
 |  | 
 | int __pthread_setcancelstate(int state, int * oldstate) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   if (state < PTHREAD_CANCEL_ENABLE || state > PTHREAD_CANCEL_DISABLE) | 
 |     return EINVAL; | 
 |   if (oldstate != NULL) *oldstate = THREAD_GETMEM(self, p_cancelstate); | 
 |   THREAD_SETMEM(self, p_cancelstate, state); | 
 |   if (THREAD_GETMEM(self, p_canceled) && | 
 |       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE && | 
 |       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS) | 
 |     __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME); | 
 |   return 0; | 
 | } | 
 | strong_alias (__pthread_setcancelstate, pthread_setcancelstate) | 
 |  | 
 | int __pthread_setcanceltype(int type, int * oldtype) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   if (type < PTHREAD_CANCEL_DEFERRED || type > PTHREAD_CANCEL_ASYNCHRONOUS) | 
 |     return EINVAL; | 
 |   if (oldtype != NULL) *oldtype = THREAD_GETMEM(self, p_canceltype); | 
 |   THREAD_SETMEM(self, p_canceltype, type); | 
 |   if (THREAD_GETMEM(self, p_canceled) && | 
 |       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE && | 
 |       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS) | 
 |     __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME); | 
 |   return 0; | 
 | } | 
 | strong_alias (__pthread_setcanceltype, pthread_setcanceltype) | 
 |  | 
 |  | 
 | /* The next two functions are similar to pthread_setcanceltype() but | 
 |    more specialized for the use in the cancelable functions like write(). | 
 |    They do not need to check parameters etc.  */ | 
 | int | 
 | attribute_hidden | 
 | __pthread_enable_asynccancel (void) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   int oldtype = THREAD_GETMEM(self, p_canceltype); | 
 |   THREAD_SETMEM(self, p_canceltype, PTHREAD_CANCEL_ASYNCHRONOUS); | 
 |   if (__builtin_expect (THREAD_GETMEM(self, p_canceled), 0) && | 
 |       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) | 
 |     __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME); | 
 |   return oldtype; | 
 | } | 
 |  | 
 | void | 
 | internal_function attribute_hidden | 
 | __pthread_disable_asynccancel (int oldtype) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   THREAD_SETMEM(self, p_canceltype, oldtype); | 
 | } | 
 |  | 
 |  | 
 | int pthread_cancel(pthread_t thread) | 
 | { | 
 |   pthread_handle handle = thread_handle(thread); | 
 |   int pid; | 
 |   int dorestart = 0; | 
 |   pthread_descr th; | 
 |   pthread_extricate_if *pextricate; | 
 |   int already_canceled; | 
 |  | 
 |   __pthread_lock(&handle->h_lock, NULL); | 
 |   if (invalid_handle(handle, thread)) { | 
 |     __pthread_unlock(&handle->h_lock); | 
 |     return ESRCH; | 
 |   } | 
 |  | 
 |   th = handle->h_descr; | 
 |  | 
 |   already_canceled = th->p_canceled; | 
 |   th->p_canceled = 1; | 
 |  | 
 |   if (th->p_cancelstate == PTHREAD_CANCEL_DISABLE || already_canceled) { | 
 |     __pthread_unlock(&handle->h_lock); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   pextricate = th->p_extricate; | 
 |   pid = th->p_pid; | 
 |  | 
 |   /* If the thread has registered an extrication interface, then | 
 |      invoke the interface. If it returns 1, then we succeeded in | 
 |      dequeuing the thread from whatever waiting object it was enqueued | 
 |      with. In that case, it is our responsibility to wake it up. | 
 |      And also to set the p_woken_by_cancel flag so the woken thread | 
 |      can tell that it was woken by cancellation. */ | 
 |  | 
 |   if (pextricate != NULL) { | 
 |     dorestart = pextricate->pu_extricate_func(pextricate->pu_object, th); | 
 |     th->p_woken_by_cancel = dorestart; | 
 |   } | 
 |  | 
 |   __pthread_unlock(&handle->h_lock); | 
 |  | 
 |   /* If the thread has suspended or is about to, then we unblock it by | 
 |      issuing a restart, instead of a cancel signal. Otherwise we send | 
 |      the cancel signal to unblock the thread from a cancellation point, | 
 |      or to initiate asynchronous cancellation. The restart is needed so | 
 |      we have proper accounting of restarts; suspend decrements the thread's | 
 |      resume count, and restart() increments it.  This also means that suspend's | 
 |      handling of the cancel signal is obsolete. */ | 
 |  | 
 |   if (dorestart) | 
 |     restart(th); | 
 |   else | 
 |     kill(pid, __pthread_sig_cancel); | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | void pthread_testcancel(void) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   if (THREAD_GETMEM(self, p_canceled) | 
 |       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) | 
 |     __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME); | 
 | } | 
 |  | 
 | void _pthread_cleanup_push(struct _pthread_cleanup_buffer * buffer, | 
 | 			   void (*routine)(void *), void * arg) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   buffer->__routine = routine; | 
 |   buffer->__arg = arg; | 
 |   buffer->__prev = THREAD_GETMEM(self, p_cleanup); | 
 |   if (buffer->__prev != NULL && FRAME_LEFT (buffer, buffer->__prev)) | 
 |     buffer->__prev = NULL; | 
 |   THREAD_SETMEM(self, p_cleanup, buffer); | 
 | } | 
 |  | 
 | void _pthread_cleanup_pop(struct _pthread_cleanup_buffer * buffer, | 
 | 			  int execute) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   if (execute) buffer->__routine(buffer->__arg); | 
 |   THREAD_SETMEM(self, p_cleanup, buffer->__prev); | 
 | } | 
 |  | 
 | void _pthread_cleanup_push_defer(struct _pthread_cleanup_buffer * buffer, | 
 | 				 void (*routine)(void *), void * arg) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   buffer->__routine = routine; | 
 |   buffer->__arg = arg; | 
 |   buffer->__canceltype = THREAD_GETMEM(self, p_canceltype); | 
 |   buffer->__prev = THREAD_GETMEM(self, p_cleanup); | 
 |   if (buffer->__prev != NULL && FRAME_LEFT (buffer, buffer->__prev)) | 
 |     buffer->__prev = NULL; | 
 |   THREAD_SETMEM(self, p_canceltype, PTHREAD_CANCEL_DEFERRED); | 
 |   THREAD_SETMEM(self, p_cleanup, buffer); | 
 | } | 
 |  | 
 | void _pthread_cleanup_pop_restore(struct _pthread_cleanup_buffer * buffer, | 
 | 				  int execute) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   if (execute) buffer->__routine(buffer->__arg); | 
 |   THREAD_SETMEM(self, p_cleanup, buffer->__prev); | 
 |   THREAD_SETMEM(self, p_canceltype, buffer->__canceltype); | 
 |   if (THREAD_GETMEM(self, p_canceled) && | 
 |       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE && | 
 |       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS) | 
 |     __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME); | 
 | } | 
 |  | 
 | extern void __rpc_thread_destroy(void); | 
 | void __pthread_perform_cleanup(char *currentframe) | 
 | { | 
 |   pthread_descr self = thread_self(); | 
 |   struct _pthread_cleanup_buffer *c = THREAD_GETMEM(self, p_cleanup); | 
 |   struct _pthread_cleanup_buffer *last; | 
 |  | 
 |   if (c != NULL) | 
 |     while (FRAME_LEFT (currentframe, c)) | 
 |       { | 
 | 	last = c; | 
 | 	c = c->__prev; | 
 |  | 
 | 	if (c == NULL || FRAME_LEFT (last, c)) | 
 | 	  { | 
 | 	    c = NULL; | 
 | 	    break; | 
 | 	  } | 
 |       } | 
 |  | 
 |   while (c != NULL) | 
 |     { | 
 |       c->__routine(c->__arg); | 
 |  | 
 |       last = c; | 
 |       c = c->__prev; | 
 |  | 
 |       if (FRAME_LEFT (last, c)) | 
 | 	break; | 
 |     } | 
 |  | 
 | #ifdef __UCLIBC_HAS_RPC__ | 
 |   /* And the TSD which needs special help.  */ | 
 |   if (THREAD_GETMEM(self, p_libc_specific[_LIBC_TSD_KEY_RPC_VARS]) != NULL) | 
 |       __rpc_thread_destroy (); | 
 | #endif | 
 | } |