1014 lines
32 KiB
C
1014 lines
32 KiB
C
/*
|
|
* pthread emulation based on kernel threads
|
|
*
|
|
* We can't use pthreads directly, so why not let libcs
|
|
* that want pthreads use Wine's own threading instead...
|
|
*
|
|
* Copyright 1999 Ove Kåven
|
|
* Copyright 2003 Alexandre Julliard
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
struct _pthread_cleanup_buffer;
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
# include <sys/socket.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_MMAN_H
|
|
#include <sys/mman.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
# include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_NAMESER_H
|
|
# include <arpa/nameser.h>
|
|
#endif
|
|
#ifdef HAVE_RESOLV_H
|
|
# include <resolv.h>
|
|
#endif
|
|
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
|
#include <valgrind/memcheck.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_SYSCALL_H
|
|
# include <sys/syscall.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_LWP_H
|
|
# include <sys/lwp.h>
|
|
#endif
|
|
#ifdef HAVE_UCONTEXT_H
|
|
# include <ucontext.h>
|
|
#endif
|
|
#ifdef HAVE_SCHED_H
|
|
#include <sched.h>
|
|
#endif
|
|
|
|
#include "wine/library.h"
|
|
#include "wine/pthread.h"
|
|
|
|
#define P_OUTPUT(stuff) write(2,stuff,strlen(stuff))
|
|
|
|
#define PSTR(str) __ASM_NAME(#str)
|
|
|
|
static struct wine_pthread_functions funcs;
|
|
|
|
/* thread descriptor */
|
|
|
|
#define FIRST_KEY 0
|
|
#define MAX_KEYS 16 /* libc6 doesn't use that many, but... */
|
|
#define MAX_TSD 16
|
|
|
|
struct pthread_descr_struct
|
|
{
|
|
char dummy[2048];
|
|
int thread_errno;
|
|
int thread_h_errno;
|
|
int cancel_state;
|
|
int cancel_type;
|
|
struct __res_state res_state;
|
|
const void *key_data[MAX_KEYS]; /* for normal pthread keys */
|
|
const void *tsd_data[MAX_TSD]; /* for libc internal tsd variables */
|
|
};
|
|
|
|
typedef struct pthread_descr_struct *pthread_descr;
|
|
|
|
static struct pthread_descr_struct initial_descr;
|
|
|
|
pthread_descr __pthread_thread_self(void)
|
|
{
|
|
struct pthread_descr_struct *descr;
|
|
if (!funcs.ptr_get_thread_data) return &initial_descr;
|
|
descr = funcs.ptr_get_thread_data();
|
|
if (!descr) return &initial_descr;
|
|
return descr;
|
|
}
|
|
|
|
static int (*libc_uselocale)(int set);
|
|
static int *libc_multiple_threads;
|
|
|
|
/***********************************************************************
|
|
* __errno_location/__error/__errno/___errno/__thr_errno
|
|
*
|
|
* Get the per-thread errno location.
|
|
*/
|
|
int *__errno_location(void) /* Linux */
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return &descr->thread_errno;
|
|
}
|
|
int *__error(void) { return __errno_location(); } /* FreeBSD */
|
|
int *__errno(void) { return __errno_location(); } /* NetBSD */
|
|
int *___errno(void) { return __errno_location(); } /* Solaris */
|
|
int *__thr_errno(void) { return __errno_location(); } /* UnixWare */
|
|
|
|
/***********************************************************************
|
|
* __h_errno_location
|
|
*
|
|
* Get the per-thread h_errno location.
|
|
*/
|
|
int *__h_errno_location(void)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return &descr->thread_h_errno;
|
|
}
|
|
|
|
struct __res_state *__res_state(void)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return &descr->res_state;
|
|
}
|
|
|
|
static inline void writejump( const char *symbol, void *dest )
|
|
{
|
|
#if defined(__GLIBC__) && defined(__i386__)
|
|
unsigned char *addr = wine_dlsym( RTLD_NEXT, symbol, NULL, 0 );
|
|
|
|
if (!addr) return;
|
|
|
|
/* write a relative jump at the function address */
|
|
mprotect((void*)((unsigned int)addr & ~(getpagesize()-1)), 5, PROT_READ|PROT_EXEC|PROT_WRITE);
|
|
addr[0] = 0xe9;
|
|
*(int *)(addr+1) = (unsigned char *)dest - (addr + 5);
|
|
mprotect((void*)((unsigned int)addr & ~(getpagesize()-1)), 5, PROT_READ|PROT_EXEC);
|
|
|
|
# ifdef HAVE_VALGRIND_MEMCHECK_H
|
|
VALGRIND_DISCARD_TRANSLATIONS( addr, 5 );
|
|
# endif
|
|
#endif /* __GLIBC__ && __i386__ */
|
|
}
|
|
|
|
/* temporary stacks used on thread exit */
|
|
#define TEMP_STACK_SIZE 1024
|
|
#define NB_TEMP_STACKS 8
|
|
static char temp_stacks[NB_TEMP_STACKS][TEMP_STACK_SIZE];
|
|
static LONG next_temp_stack; /* next temp stack to use */
|
|
|
|
/***********************************************************************
|
|
* get_temp_stack
|
|
*
|
|
* Get a temporary stack address to run the thread exit code on.
|
|
*/
|
|
inline static char *get_temp_stack(void)
|
|
{
|
|
unsigned int next = interlocked_xchg_add( &next_temp_stack, 1 );
|
|
return temp_stacks[next % NB_TEMP_STACKS] + TEMP_STACK_SIZE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* cleanup_thread
|
|
*
|
|
* Cleanup the remains of a thread. Runs on a temporary stack.
|
|
*/
|
|
static void cleanup_thread( void *ptr )
|
|
{
|
|
/* copy the info structure since it is on the stack we will free */
|
|
struct wine_pthread_thread_info info = *(struct wine_pthread_thread_info *)ptr;
|
|
wine_ldt_free_fs( info.teb_sel );
|
|
munmap( info.stack_base, info.stack_size );
|
|
munmap( info.teb_base, info.teb_size );
|
|
#ifdef HAVE__LWP_CREATE
|
|
_lwp_exit();
|
|
#endif
|
|
_exit( info.exit_status );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_init_process
|
|
*
|
|
* Initialization for a newly created process.
|
|
*/
|
|
void wine_pthread_init_process( const struct wine_pthread_functions *functions )
|
|
{
|
|
memcpy( &funcs, functions, min(functions->size,sizeof(funcs)) );
|
|
funcs.ptr_set_thread_data( &initial_descr );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_init_thread
|
|
*
|
|
* Initialization for a newly created thread.
|
|
*/
|
|
void wine_pthread_init_thread( struct wine_pthread_thread_info *info )
|
|
{
|
|
struct pthread_descr_struct *descr;
|
|
|
|
if (funcs.ptr_set_thread_data)
|
|
{
|
|
descr = calloc( 1, sizeof(*descr) );
|
|
funcs.ptr_set_thread_data( descr );
|
|
if (libc_multiple_threads) *libc_multiple_threads = 1;
|
|
}
|
|
else /* first thread */
|
|
{
|
|
descr = &initial_descr;
|
|
writejump( "__errno_location", __errno_location );
|
|
writejump( "__h_errno_location", __h_errno_location );
|
|
writejump( "__res_state", __res_state );
|
|
}
|
|
descr->cancel_state = PTHREAD_CANCEL_ENABLE;
|
|
descr->cancel_type = PTHREAD_CANCEL_ASYNCHRONOUS;
|
|
if (libc_uselocale) libc_uselocale( -1 /*LC_GLOBAL_LOCALE*/ );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_create_thread
|
|
*/
|
|
int wine_pthread_create_thread( struct wine_pthread_thread_info *info )
|
|
{
|
|
if (!info->stack_base)
|
|
{
|
|
info->stack_base = wine_anon_mmap( NULL, info->stack_size,
|
|
PROT_READ | PROT_WRITE | PROT_EXEC, 0 );
|
|
if (info->stack_base == (void *)-1) return -1;
|
|
}
|
|
#ifdef HAVE_CLONE
|
|
if (clone( (int (*)(void *))info->entry, (char *)info->stack_base + info->stack_size,
|
|
CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD, info ) < 0)
|
|
return -1;
|
|
return 0;
|
|
#elif defined(HAVE_RFORK)
|
|
{
|
|
void **sp = (void **)((char *)info->stack_base + info->stack_size);
|
|
*--sp = info;
|
|
*--sp = 0;
|
|
*--sp = info->entry;
|
|
__asm__ __volatile__(
|
|
"pushl %2;\n\t" /* flags */
|
|
"pushl $0;\n\t" /* 0 ? */
|
|
"movl %1,%%eax;\n\t" /* SYS_rfork */
|
|
".byte 0x9a; .long 0; .word 7;\n\t" /* lcall 7:0... FreeBSD syscall */
|
|
"cmpl $0, %%edx;\n\t"
|
|
"je 1f;\n\t"
|
|
"movl %0,%%esp;\n\t" /* child -> new thread */
|
|
"ret;\n"
|
|
"1:\n\t" /* parent -> caller thread */
|
|
"addl $8,%%esp" :
|
|
: "r" (sp), "g" (SYS_rfork), "g" (RFPROC | RFMEM | RFTHREAD)
|
|
: "eax", "edx");
|
|
return 0;
|
|
}
|
|
#elif defined(HAVE__LWP_CREATE)
|
|
{
|
|
ucontext_t context;
|
|
_lwp_makecontext( &context, (void(*)(void *))info->entry, info,
|
|
NULL, info->stack_base, info->stack_size );
|
|
if ( _lwp_create( &context, 0, NULL ) )
|
|
return -1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_init_current_teb
|
|
*
|
|
* Set the current TEB for a new thread.
|
|
*/
|
|
void wine_pthread_init_current_teb( struct wine_pthread_thread_info *info )
|
|
{
|
|
#ifdef __i386__
|
|
/* On the i386, the current thread is in the %fs register */
|
|
LDT_ENTRY fs_entry;
|
|
|
|
wine_ldt_set_base( &fs_entry, info->teb_base );
|
|
wine_ldt_set_limit( &fs_entry, info->teb_size - 1 );
|
|
wine_ldt_set_flags( &fs_entry, WINE_LDT_FLAGS_DATA|WINE_LDT_FLAGS_32BIT );
|
|
wine_ldt_init_fs( info->teb_sel, &fs_entry );
|
|
#elif defined(HAVE__LWP_CREATE)
|
|
/* On non-i386 Solaris, we use the LWP private pointer */
|
|
_lwp_setprivate( info->teb_base );
|
|
#elif defined(__powerpc__)
|
|
/* On PowerPC, the current TEB is in the gpr13 register */
|
|
# ifdef __APPLE__
|
|
__asm__ __volatile__("mr r13, %0" : : "r" (info->teb_base));
|
|
# else
|
|
__asm__ __volatile__("mr 2, %0" : : "r" (info->teb_base));
|
|
# endif
|
|
#elif defined(__ALPHA__)
|
|
/* FIXME: On Alpha, the current TEB is not accessible to user-space */
|
|
/* __asm__ __volatile__();*/
|
|
#else
|
|
# error You must implement wine_pthread_init_current_teb for your platform
|
|
#endif
|
|
|
|
/* set pid and tid */
|
|
info->pid = getpid();
|
|
#ifdef HAVE__LWP_SELF
|
|
info->tid = _lwp_self();
|
|
#else
|
|
info->tid = -1;
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_get_current_teb
|
|
*/
|
|
void *wine_pthread_get_current_teb(void)
|
|
{
|
|
void *ret;
|
|
|
|
#ifdef __i386__
|
|
__asm__( ".byte 0x64\n\tmovl 0x18,%0" : "=r" (ret) );
|
|
#elif defined(HAVE__LWP_CREATE)
|
|
ret = _lwp_getprivate();
|
|
#elif defined(__powerpc__)
|
|
# ifdef __APPLE__
|
|
__asm__( "mr %0,r13" : "=r" (ret) );
|
|
# else
|
|
__asm__( "mr %0,2" : "=r" (ret) );
|
|
# endif
|
|
#elif defined(__ALPHA__)
|
|
/* 0x00ab is the PAL opcode for rdteb */
|
|
__asm__( "lda $30,8($30)\n\t"
|
|
"stq $0,0($30)\n\t"
|
|
"call_pal 0x00ab\n\t"
|
|
"mov $0,%0\n\t"
|
|
"ldq $0,0($30)\n\t"
|
|
"lda $30,-8($30)" : "=r" (ret) );
|
|
#else
|
|
# error wine_pthread_get_current_teb not defined for this architecture
|
|
#endif /* __i386__ */
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_exit_thread
|
|
*/
|
|
void wine_pthread_exit_thread( struct wine_pthread_thread_info *info )
|
|
{
|
|
wine_switch_to_stack( cleanup_thread, info, get_temp_stack() );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* wine_pthread_abort_thread
|
|
*/
|
|
void wine_pthread_abort_thread( int status )
|
|
{
|
|
#ifdef HAVE__LWP_CREATE
|
|
_lwp_exit();
|
|
#endif
|
|
_exit( status );
|
|
}
|
|
|
|
|
|
/* Currently this probably works only for glibc2,
|
|
* which checks for the presence of double-underscore-prepended
|
|
* pthread primitives, and use them if available.
|
|
* If they are not available, the libc defaults to
|
|
* non-threadsafe operation (not good). */
|
|
|
|
#if defined(HAVE_PTHREAD_H) && (defined(__GLIBC__) || defined(__FreeBSD__))
|
|
|
|
/* adapt as necessary (a construct like this is used in glibc sources) */
|
|
#define strong_alias(orig, alias) \
|
|
asm(".globl " PSTR(alias) "\n" \
|
|
"\t.set " PSTR(alias) "," PSTR(orig))
|
|
|
|
struct fork_block;
|
|
|
|
/* pthread functions redirection */
|
|
|
|
struct pthread_functions
|
|
{
|
|
pid_t (*ptr_pthread_fork) (struct fork_block *);
|
|
int (*ptr_pthread_attr_destroy) (pthread_attr_t *);
|
|
int (*ptr___pthread_attr_init_2_0) (pthread_attr_t *);
|
|
int (*ptr___pthread_attr_init_2_1) (pthread_attr_t *);
|
|
int (*ptr_pthread_attr_getdetachstate) (const pthread_attr_t *, int *);
|
|
int (*ptr_pthread_attr_setdetachstate) (pthread_attr_t *, int);
|
|
int (*ptr_pthread_attr_getinheritsched) (const pthread_attr_t *, int *);
|
|
int (*ptr_pthread_attr_setinheritsched) (pthread_attr_t *, int);
|
|
int (*ptr_pthread_attr_getschedparam) (const pthread_attr_t *, struct sched_param *);
|
|
int (*ptr_pthread_attr_setschedparam) (pthread_attr_t *, const struct sched_param *);
|
|
int (*ptr_pthread_attr_getschedpolicy) (const pthread_attr_t *, int *);
|
|
int (*ptr_pthread_attr_setschedpolicy) (pthread_attr_t *, int);
|
|
int (*ptr_pthread_attr_getscope) (const pthread_attr_t *, int *);
|
|
int (*ptr_pthread_attr_setscope) (pthread_attr_t *, int);
|
|
int (*ptr_pthread_condattr_destroy) (pthread_condattr_t *);
|
|
int (*ptr_pthread_condattr_init) (pthread_condattr_t *);
|
|
int (*ptr___pthread_cond_broadcast) (pthread_cond_t *);
|
|
int (*ptr___pthread_cond_destroy) (pthread_cond_t *);
|
|
int (*ptr___pthread_cond_init) (pthread_cond_t *, const pthread_condattr_t *);
|
|
int (*ptr___pthread_cond_signal) (pthread_cond_t *);
|
|
int (*ptr___pthread_cond_wait) (pthread_cond_t *, pthread_mutex_t *);
|
|
int (*ptr_pthread_equal) (pthread_t, pthread_t);
|
|
void (*ptr___pthread_exit) (void *);
|
|
int (*ptr_pthread_getschedparam) (pthread_t, int *, struct sched_param *);
|
|
int (*ptr_pthread_setschedparam) (pthread_t, int, const struct sched_param *);
|
|
int (*ptr_pthread_mutex_destroy) (pthread_mutex_t *);
|
|
int (*ptr_pthread_mutex_init) (pthread_mutex_t *, const pthread_mutexattr_t *);
|
|
int (*ptr_pthread_mutex_lock) (pthread_mutex_t *);
|
|
int (*ptr_pthread_mutex_trylock) (pthread_mutex_t *);
|
|
int (*ptr_pthread_mutex_unlock) (pthread_mutex_t *);
|
|
pthread_t (*ptr_pthread_self) (void);
|
|
int (*ptr_pthread_setcancelstate) (int, int *);
|
|
int (*ptr_pthread_setcanceltype) (int, int *);
|
|
void (*ptr_pthread_do_exit) (void *retval, char *currentframe);
|
|
void (*ptr_pthread_cleanup_upto) (jmp_buf target, char *targetframe);
|
|
pthread_descr (*ptr_pthread_thread_self) (void);
|
|
int (*ptr_pthread_internal_tsd_set) (int key, const void *pointer);
|
|
void * (*ptr_pthread_internal_tsd_get) (int key);
|
|
void ** __attribute__ ((__const__)) (*ptr_pthread_internal_tsd_address) (int key);
|
|
int (*ptr_pthread_sigaction) (int sig, const struct sigaction * act, struct sigaction *oact);
|
|
int (*ptr_pthread_sigwait) (const sigset_t *set, int *sig);
|
|
int (*ptr_pthread_raise) (int sig);
|
|
int (*ptr___pthread_cond_timedwait) (pthread_cond_t *, pthread_mutex_t *, const struct timespec *);
|
|
void (*ptr__pthread_cleanup_push) (struct _pthread_cleanup_buffer * buffer, void (*routine)(void *), void * arg);
|
|
void (*ptr__pthread_cleanup_pop) (struct _pthread_cleanup_buffer * buffer, int execute);
|
|
};
|
|
|
|
static pid_t (*libc_fork)(void);
|
|
static int (*libc_sigaction)(int signum, const struct sigaction *act, struct sigaction *oldact);
|
|
static int *(*libc_pthread_init)( const struct pthread_functions *funcs );
|
|
|
|
static struct pthread_functions libc_pthread_functions;
|
|
|
|
strong_alias(__pthread_thread_self, pthread_thread_self);
|
|
|
|
/* redefine this to prevent libpthread from overriding our function pointers */
|
|
int *__libc_pthread_init( const struct pthread_functions *funcs )
|
|
{
|
|
return libc_multiple_threads;
|
|
}
|
|
|
|
typedef struct _wine_cleanup {
|
|
void (*routine)(void *);
|
|
void *arg;
|
|
} *wine_cleanup;
|
|
|
|
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*
|
|
(*start_routine)(void *), void* arg)
|
|
{
|
|
assert( funcs.ptr_pthread_create );
|
|
return funcs.ptr_pthread_create( thread, attr, start_routine, arg );
|
|
}
|
|
|
|
int pthread_cancel(pthread_t thread)
|
|
{
|
|
assert( funcs.ptr_pthread_cancel );
|
|
return funcs.ptr_pthread_cancel( thread );
|
|
}
|
|
|
|
int pthread_join(pthread_t thread, void **value_ptr)
|
|
{
|
|
assert( funcs.ptr_pthread_join );
|
|
return funcs.ptr_pthread_join( thread, value_ptr );
|
|
}
|
|
|
|
int pthread_detach(pthread_t thread)
|
|
{
|
|
assert( funcs.ptr_pthread_detach );
|
|
return funcs.ptr_pthread_detach( thread );
|
|
}
|
|
|
|
/* FIXME: we have no equivalents in win32 for the policys */
|
|
/* so just keep this as a stub */
|
|
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)
|
|
{
|
|
P_OUTPUT("FIXME:pthread_attr_setschedpolicy\n");
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME: no win32 equivalent for scope */
|
|
int pthread_attr_setscope(pthread_attr_t *attr, int scope)
|
|
{
|
|
P_OUTPUT("FIXME:pthread_attr_setscope\n");
|
|
return 0; /* return success */
|
|
}
|
|
|
|
/* FIXME: no win32 equivalent for schedule param */
|
|
int pthread_attr_setschedparam(pthread_attr_t *attr,
|
|
const struct sched_param *param)
|
|
{
|
|
P_OUTPUT("FIXME:pthread_attr_setschedparam\n");
|
|
return 0; /* return success */
|
|
}
|
|
|
|
/* FIXME */
|
|
int pthread_attr_setstack(pthread_attr_t *attr, void *addr, size_t size)
|
|
{
|
|
return 0; /* return success */
|
|
}
|
|
|
|
int __pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
|
|
{
|
|
static pthread_once_t the_once = PTHREAD_ONCE_INIT;
|
|
long once_now;
|
|
|
|
memcpy(&once_now,&the_once,sizeof(once_now));
|
|
if (interlocked_cmpxchg((long*)once_control, once_now+1, once_now) == once_now)
|
|
(*init_routine)();
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_once, pthread_once);
|
|
|
|
void __pthread_kill_other_threads_np(void)
|
|
{
|
|
/* we don't need to do anything here */
|
|
}
|
|
strong_alias(__pthread_kill_other_threads_np, pthread_kill_other_threads_np);
|
|
|
|
/***** atfork *****/
|
|
|
|
#define MAX_ATFORK 8 /* libc doesn't need that many anyway */
|
|
|
|
static pthread_mutex_t atfork_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
typedef void (*atfork_handler)();
|
|
static atfork_handler atfork_prepare[MAX_ATFORK];
|
|
static atfork_handler atfork_parent[MAX_ATFORK];
|
|
static atfork_handler atfork_child[MAX_ATFORK];
|
|
static int atfork_count;
|
|
|
|
int __pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
|
|
{
|
|
pthread_mutex_lock( &atfork_mutex );
|
|
assert( atfork_count < MAX_ATFORK );
|
|
atfork_prepare[atfork_count] = prepare;
|
|
atfork_parent[atfork_count] = parent;
|
|
atfork_child[atfork_count] = child;
|
|
atfork_count++;
|
|
pthread_mutex_unlock( &atfork_mutex );
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_atfork, pthread_atfork);
|
|
|
|
pid_t __fork(void)
|
|
{
|
|
pid_t pid;
|
|
int i;
|
|
|
|
if (!libc_fork)
|
|
{
|
|
libc_fork = wine_dlsym( RTLD_NEXT, "fork", NULL, 0 );
|
|
assert( libc_fork );
|
|
}
|
|
pthread_mutex_lock( &atfork_mutex );
|
|
/* prepare handlers are called in reverse insertion order */
|
|
for (i = atfork_count - 1; i >= 0; i--) if (atfork_prepare[i]) atfork_prepare[i]();
|
|
if (!(pid = libc_fork()))
|
|
{
|
|
pthread_mutex_init( &atfork_mutex, NULL );
|
|
for (i = 0; i < atfork_count; i++) if (atfork_child[i]) atfork_child[i]();
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < atfork_count; i++) if (atfork_parent[i]) atfork_parent[i]();
|
|
pthread_mutex_unlock( &atfork_mutex );
|
|
}
|
|
return pid;
|
|
}
|
|
strong_alias(__fork, fork);
|
|
|
|
/***** MUTEXES *****/
|
|
|
|
int __pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
|
|
{
|
|
if (!funcs.ptr_pthread_mutex_init) return 0;
|
|
return funcs.ptr_pthread_mutex_init( mutex, mutexattr );
|
|
}
|
|
strong_alias(__pthread_mutex_init, pthread_mutex_init);
|
|
|
|
int __pthread_mutex_lock(pthread_mutex_t *mutex)
|
|
{
|
|
if (!funcs.ptr_pthread_mutex_lock) return 0;
|
|
return funcs.ptr_pthread_mutex_lock( mutex );
|
|
}
|
|
strong_alias(__pthread_mutex_lock, pthread_mutex_lock);
|
|
|
|
int __pthread_mutex_trylock(pthread_mutex_t *mutex)
|
|
{
|
|
if (!funcs.ptr_pthread_mutex_trylock) return 0;
|
|
return funcs.ptr_pthread_mutex_trylock( mutex );
|
|
}
|
|
strong_alias(__pthread_mutex_trylock, pthread_mutex_trylock);
|
|
|
|
int __pthread_mutex_unlock(pthread_mutex_t *mutex)
|
|
{
|
|
if (!funcs.ptr_pthread_mutex_unlock) return 0;
|
|
return funcs.ptr_pthread_mutex_unlock( mutex );
|
|
}
|
|
strong_alias(__pthread_mutex_unlock, pthread_mutex_unlock);
|
|
|
|
int __pthread_mutex_destroy(pthread_mutex_t *mutex)
|
|
{
|
|
if (!funcs.ptr_pthread_mutex_destroy) return 0;
|
|
return funcs.ptr_pthread_mutex_destroy( mutex );
|
|
}
|
|
strong_alias(__pthread_mutex_destroy, pthread_mutex_destroy);
|
|
|
|
|
|
/***** MUTEX ATTRIBUTES *****/
|
|
/* just dummies, since critical sections are always recursive */
|
|
|
|
int __pthread_mutexattr_init(pthread_mutexattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_init, pthread_mutexattr_init);
|
|
|
|
int __pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_destroy, pthread_mutexattr_destroy);
|
|
|
|
int __pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_setkind_np, pthread_mutexattr_setkind_np);
|
|
|
|
int __pthread_mutexattr_getkind_np(pthread_mutexattr_t *attr, int *kind)
|
|
{
|
|
*kind = PTHREAD_MUTEX_RECURSIVE;
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_getkind_np, pthread_mutexattr_getkind_np);
|
|
|
|
int __pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_settype, pthread_mutexattr_settype);
|
|
|
|
int __pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind)
|
|
{
|
|
*kind = PTHREAD_MUTEX_RECURSIVE;
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_mutexattr_gettype, pthread_mutexattr_gettype);
|
|
|
|
|
|
/***** THREAD-SPECIFIC VARIABLES (KEYS) *****/
|
|
|
|
int __pthread_key_create(pthread_key_t *key, void (*destr_function)(void *))
|
|
{
|
|
static long keycnt = FIRST_KEY;
|
|
*key = interlocked_xchg_add(&keycnt, 1);
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_key_create, pthread_key_create);
|
|
|
|
int __pthread_key_delete(pthread_key_t key)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_key_delete, pthread_key_delete);
|
|
|
|
int __pthread_setspecific(pthread_key_t key, const void *pointer)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
descr->key_data[key] = pointer;
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_setspecific, pthread_setspecific);
|
|
|
|
void *__pthread_getspecific(pthread_key_t key)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return (void *)descr->key_data[key];
|
|
}
|
|
strong_alias(__pthread_getspecific, pthread_getspecific);
|
|
|
|
static int pthread_internal_tsd_set( int key, const void *pointer )
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
descr->tsd_data[key] = pointer;
|
|
return 0;
|
|
}
|
|
int (*__libc_internal_tsd_set)(int, const void *) = pthread_internal_tsd_set;
|
|
|
|
static void *pthread_internal_tsd_get( int key )
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return (void *)descr->tsd_data[key];
|
|
}
|
|
void* (*__libc_internal_tsd_get)(int) = pthread_internal_tsd_get;
|
|
|
|
static void ** __attribute__((const)) pthread_internal_tsd_address( int key )
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
return (void **)&descr->tsd_data[key];
|
|
}
|
|
void** (*__libc_internal_tsd_address)(int) = pthread_internal_tsd_address;
|
|
|
|
/***** "EXCEPTION" FRAMES *****/
|
|
/* not implemented right now */
|
|
|
|
void _pthread_cleanup_push(struct _pthread_cleanup_buffer *buffer, void (*routine)(void *), void *arg)
|
|
{
|
|
((wine_cleanup)buffer)->routine = routine;
|
|
((wine_cleanup)buffer)->arg = arg;
|
|
}
|
|
|
|
void _pthread_cleanup_pop(struct _pthread_cleanup_buffer *buffer, int execute)
|
|
{
|
|
if (execute) (*(((wine_cleanup)buffer)->routine))(((wine_cleanup)buffer)->arg);
|
|
}
|
|
|
|
void _pthread_cleanup_push_defer(struct _pthread_cleanup_buffer *buffer, void (*routine)(void *), void *arg)
|
|
{
|
|
_pthread_cleanup_push(buffer, routine, arg);
|
|
}
|
|
|
|
void _pthread_cleanup_pop_restore(struct _pthread_cleanup_buffer *buffer, int execute)
|
|
{
|
|
_pthread_cleanup_pop(buffer, execute);
|
|
}
|
|
|
|
void __pthread_cleanup_upto(jmp_buf target, char *frame)
|
|
{
|
|
/* FIXME */
|
|
}
|
|
|
|
/***** CONDITIONS *****/
|
|
|
|
int __pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_init) return 0;
|
|
return funcs.ptr_pthread_cond_init(cond, cond_attr);
|
|
}
|
|
strong_alias(__pthread_cond_init, pthread_cond_init);
|
|
|
|
int __pthread_cond_destroy(pthread_cond_t *cond)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_destroy) return 0;
|
|
return funcs.ptr_pthread_cond_destroy(cond);
|
|
}
|
|
strong_alias(__pthread_cond_destroy, pthread_cond_destroy);
|
|
|
|
int __pthread_cond_signal(pthread_cond_t *cond)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_signal) return 0;
|
|
return funcs.ptr_pthread_cond_signal(cond);
|
|
}
|
|
strong_alias(__pthread_cond_signal, pthread_cond_signal);
|
|
|
|
int __pthread_cond_broadcast(pthread_cond_t *cond)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_broadcast) return 0;
|
|
return funcs.ptr_pthread_cond_broadcast(cond);
|
|
}
|
|
strong_alias(__pthread_cond_broadcast, pthread_cond_broadcast);
|
|
|
|
int __pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_wait) return 0;
|
|
return funcs.ptr_pthread_cond_wait(cond, mutex);
|
|
}
|
|
strong_alias(__pthread_cond_wait, pthread_cond_wait);
|
|
|
|
int __pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
|
|
{
|
|
if (!funcs.ptr_pthread_cond_timedwait) return 0;
|
|
return funcs.ptr_pthread_cond_timedwait(cond, mutex, abstime);
|
|
}
|
|
strong_alias(__pthread_cond_timedwait, pthread_cond_timedwait);
|
|
|
|
/**** CONDITION ATTRIBUTES *****/
|
|
/* not implemented right now */
|
|
|
|
int pthread_condattr_init(pthread_condattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int pthread_condattr_destroy(pthread_condattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/***** READ-WRITE LOCKS *****/
|
|
|
|
int __pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlock_attr)
|
|
{
|
|
assert( funcs.ptr_pthread_rwlock_init );
|
|
return funcs.ptr_pthread_rwlock_init( rwlock, rwlock_attr );
|
|
}
|
|
strong_alias(__pthread_rwlock_init, pthread_rwlock_init);
|
|
|
|
int __pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
|
|
{
|
|
assert( funcs.ptr_pthread_rwlock_destroy );
|
|
return funcs.ptr_pthread_rwlock_destroy( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_destroy, pthread_rwlock_destroy);
|
|
|
|
int __pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
|
|
{
|
|
if (!funcs.ptr_pthread_rwlock_rdlock) return 0;
|
|
return funcs.ptr_pthread_rwlock_rdlock( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_rdlock, pthread_rwlock_rdlock);
|
|
|
|
int __pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
|
|
{
|
|
assert( funcs.ptr_pthread_rwlock_tryrdlock );
|
|
return funcs.ptr_pthread_rwlock_tryrdlock( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
|
|
|
|
int __pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
|
|
{
|
|
assert( funcs.ptr_pthread_rwlock_wrlock );
|
|
return funcs.ptr_pthread_rwlock_wrlock( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_wrlock, pthread_rwlock_wrlock);
|
|
|
|
int __pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
|
|
{
|
|
assert( funcs.ptr_pthread_rwlock_trywrlock );
|
|
return funcs.ptr_pthread_rwlock_trywrlock( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_trywrlock, pthread_rwlock_trywrlock);
|
|
|
|
int __pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
|
|
{
|
|
if (!funcs.ptr_pthread_rwlock_unlock) return 0;
|
|
return funcs.ptr_pthread_rwlock_unlock( rwlock );
|
|
}
|
|
strong_alias(__pthread_rwlock_unlock, pthread_rwlock_unlock);
|
|
|
|
/**** READ-WRITE LOCK ATTRIBUTES *****/
|
|
/* not implemented right now */
|
|
|
|
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int __pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
|
|
{
|
|
return 0;
|
|
}
|
|
strong_alias(__pthread_rwlockattr_destroy, pthread_rwlockattr_destroy);
|
|
|
|
int pthread_rwlockattr_getkind_np(const pthread_rwlockattr_t *attr, int *pref)
|
|
{
|
|
*pref = 0;
|
|
return 0;
|
|
}
|
|
|
|
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/***** MISC *****/
|
|
|
|
pthread_t pthread_self(void)
|
|
{
|
|
assert( funcs.ptr_pthread_self );
|
|
return funcs.ptr_pthread_self();
|
|
}
|
|
|
|
int pthread_equal(pthread_t thread1, pthread_t thread2)
|
|
{
|
|
assert( funcs.ptr_pthread_equal );
|
|
return funcs.ptr_pthread_equal( thread1, thread2 );
|
|
}
|
|
|
|
void __pthread_do_exit(void *retval, char *currentframe)
|
|
{
|
|
assert( funcs.ptr_pthread_exit );
|
|
return funcs.ptr_pthread_exit( retval, currentframe );
|
|
}
|
|
|
|
void __pthread_exit(void *retval)
|
|
{
|
|
__pthread_do_exit( retval, NULL );
|
|
}
|
|
strong_alias(__pthread_exit, pthread_exit);
|
|
|
|
int pthread_setcancelstate(int state, int *oldstate)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
if (oldstate) *oldstate = descr->cancel_state;
|
|
descr->cancel_state = state;
|
|
return 0;
|
|
}
|
|
|
|
int pthread_setcanceltype(int type, int *oldtype)
|
|
{
|
|
pthread_descr descr = __pthread_thread_self();
|
|
if (oldtype) *oldtype = descr->cancel_type;
|
|
descr->cancel_type = type;
|
|
return 0;
|
|
}
|
|
|
|
/***** ANTI-OVERRIDES *****/
|
|
/* pthreads tries to override these, point them back to libc */
|
|
|
|
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
|
|
{
|
|
if (!libc_sigaction)
|
|
{
|
|
libc_sigaction = wine_dlsym( RTLD_NEXT, "sigaction", NULL, 0 );
|
|
assert( libc_sigaction );
|
|
}
|
|
return libc_sigaction(signum, act, oldact);
|
|
}
|
|
|
|
void __pthread_initialize(void)
|
|
{
|
|
static int done;
|
|
|
|
if (!done)
|
|
{
|
|
done = 1;
|
|
libc_fork = wine_dlsym( RTLD_NEXT, "fork", NULL, 0 );
|
|
libc_sigaction = wine_dlsym( RTLD_NEXT, "sigaction", NULL, 0 );
|
|
libc_uselocale = wine_dlsym( RTLD_DEFAULT, "uselocale", NULL, 0 );
|
|
libc_pthread_init = wine_dlsym( RTLD_NEXT, "__libc_pthread_init", NULL, 0 );
|
|
if (libc_pthread_init) libc_multiple_threads = libc_pthread_init( &libc_pthread_functions );
|
|
}
|
|
}
|
|
DECL_GLOBAL_CONSTRUCTOR(init) { __pthread_initialize(); }
|
|
|
|
static struct pthread_functions libc_pthread_functions =
|
|
{
|
|
NULL, /* ptr_pthread_fork */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_destroy */
|
|
NULL, /* FIXME */ /* ptr___pthread_attr_init_2_0 */
|
|
NULL, /* FIXME */ /* ptr___pthread_attr_init_2_1 */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_getdetachstate */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_setdetachstate */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_getinheritsched */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_setinheritsched */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_getschedparam */
|
|
pthread_attr_setschedparam, /* ptr_pthread_attr_setschedparam */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_getschedpolicy */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_setschedpolicy */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_getscope */
|
|
NULL, /* FIXME */ /* ptr_pthread_attr_setscope */
|
|
pthread_condattr_destroy, /* ptr_pthread_condattr_destroy */
|
|
pthread_condattr_init, /* ptr_pthread_condattr_init */
|
|
__pthread_cond_broadcast, /* ptr___pthread_cond_broadcast */
|
|
__pthread_cond_destroy, /* ptr___pthread_cond_destroy */
|
|
__pthread_cond_init, /* ptr___pthread_cond_init */
|
|
__pthread_cond_signal, /* ptr___pthread_cond_signal */
|
|
__pthread_cond_wait, /* ptr___pthread_cond_wait */
|
|
pthread_equal, /* ptr_pthread_equal */
|
|
__pthread_exit, /* ptr___pthread_exit */
|
|
NULL, /* FIXME */ /* ptr_pthread_getschedparam */
|
|
NULL, /* FIXME */ /* ptr_pthread_setschedparam */
|
|
__pthread_mutex_destroy, /* ptr_pthread_mutex_destroy */
|
|
__pthread_mutex_init, /* ptr_pthread_mutex_init */
|
|
__pthread_mutex_lock, /* ptr_pthread_mutex_lock */
|
|
__pthread_mutex_trylock, /* ptr_pthread_mutex_trylock */
|
|
__pthread_mutex_unlock, /* ptr_pthread_mutex_unlock */
|
|
pthread_self, /* ptr_pthread_self */
|
|
pthread_setcancelstate, /* ptr_pthread_setcancelstate */
|
|
pthread_setcanceltype, /* ptr_pthread_setcanceltype */
|
|
__pthread_do_exit, /* ptr_pthread_do_exit */
|
|
__pthread_cleanup_upto, /* ptr_pthread_cleanup_upto */
|
|
__pthread_thread_self, /* ptr_pthread_thread_self */
|
|
pthread_internal_tsd_set, /* ptr_pthread_internal_tsd_set */
|
|
pthread_internal_tsd_get, /* ptr_pthread_internal_tsd_get */
|
|
pthread_internal_tsd_address, /* ptr_pthread_internal_tsd_address */
|
|
NULL, /* ptr_pthread_sigaction */
|
|
NULL, /* ptr_pthread_sigwait */
|
|
NULL, /* ptr_pthread_raise */
|
|
__pthread_cond_timedwait, /* ptr___pthread_cond_timedwait */
|
|
_pthread_cleanup_push, /* ptr__pthread_cleanup_push */
|
|
_pthread_cleanup_pop /* ptr__pthread_cleanup_pop */
|
|
};
|
|
|
|
#endif /* HAVE_PTHREAD_H && (__GLIBC__ || __FREEBSD__) */
|