#include <sys/param.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <sys/file.h>
#include <machine/sysarch.h>
#include <machine/tls.h>
#include "debug.h"
#include "rtld.h"
#include "rtld_machdep.h"
extern pid_t __sys_getpid(void);
#define WAFLAG 0x1
#define SLFLAG 0x2
#define RC_INCR 0x4
struct Struct_Lock {
volatile u_int lock;
int count;
void *owner;
sigset_t savesigmask;
} __cachealign;
#define cpu_ccfence() __asm __volatile("" : : : "memory")
static sigset_t fullsigmask;
struct Struct_Lock phdr_lock;
struct Struct_Lock bind_lock;
struct Struct_Lock libc_lock;
rtld_lock_t rtld_phdr_lock = &phdr_lock;
rtld_lock_t rtld_bind_lock = &bind_lock;
rtld_lock_t rtld_libc_lock = &libc_lock;
static int _rtld_isthreaded;
void _rtld_setthreaded(int threaded);
void
_rtld_setthreaded(int threaded)
{
_rtld_isthreaded = threaded;
}
static __inline
void *
myid(void)
{
if (_rtld_isthreaded) {
return(tls_get_tcb());
}
return (void *)(intptr_t)1;
}
void
rlock_acquire(rtld_lock_t lock, RtldLockState *state)
{
void *tid = myid();
int v;
v = lock->lock;
cpu_ccfence();
for (;;) {
if ((v & WAFLAG) == 0) {
if (atomic_fcmpset_int(&lock->lock, &v, v + RC_INCR)) {
state->lockstate = RTLD_LOCK_RLOCKED;
break;
}
} else {
if (lock->owner == tid) {
++lock->count;
state->lockstate = RTLD_LOCK_WLOCKED;
break;
}
if (atomic_fcmpset_int(&lock->lock, &v, v | SLFLAG)) {
umtx_sleep(&lock->lock, v, 0);
}
}
cpu_ccfence();
}
}
void
wlock_acquire(rtld_lock_t lock, RtldLockState *state)
{
void *tid = myid();
sigset_t tmp_oldsigmask;
int v;
if (lock->owner == tid) {
++lock->count;
state->lockstate = RTLD_LOCK_WLOCKED;
return;
}
sigprocmask(SIG_BLOCK, &fullsigmask, &tmp_oldsigmask);
v = lock->lock;
for (;;) {
if ((v & ~SLFLAG) == 0) {
if (atomic_fcmpset_int(&lock->lock, &v, WAFLAG))
break;
} else {
if (atomic_fcmpset_int(&lock->lock, &v, v | SLFLAG)) {
umtx_sleep(&lock->lock, v, 0);
}
}
cpu_ccfence();
}
lock->owner = tid;
lock->count = 1;
lock->savesigmask = tmp_oldsigmask;
state->lockstate = RTLD_LOCK_WLOCKED;
}
void
lock_release(rtld_lock_t lock, RtldLockState *state)
{
sigset_t tmp_oldsigmask;
int v;
if (state->lockstate == RTLD_LOCK_UNLOCKED)
return;
if ((lock->lock & WAFLAG) == 0) {
v = atomic_fetchadd_int(&lock->lock, -RC_INCR) - RC_INCR;
if (v == SLFLAG) {
atomic_clear_int(&lock->lock, SLFLAG);
umtx_wakeup(&lock->lock, 0);
}
} else if (--lock->count == 0) {
tmp_oldsigmask = lock->savesigmask;
lock->owner = NULL;
v = atomic_fetchadd_int(&lock->lock, -WAFLAG) - WAFLAG;
if (v == SLFLAG) {
atomic_clear_int(&lock->lock, SLFLAG);
umtx_wakeup(&lock->lock, 0);
}
sigprocmask(SIG_SETMASK, &tmp_oldsigmask, NULL);
}
state->lockstate = RTLD_LOCK_UNLOCKED;
}
static
void
lock_reset(rtld_lock_t lock)
{
memset(lock, 0, sizeof(*lock));
}
void
lock_upgrade(rtld_lock_t lock, RtldLockState *state)
{
if (state == NULL)
return;
if (state->lockstate == RTLD_LOCK_RLOCKED) {
lock_release(lock, state);
wlock_acquire(lock, state);
}
}
void
lock_restart_for_upgrade(RtldLockState *state)
{
if (state == NULL)
return;
switch (state->lockstate) {
case RTLD_LOCK_UNLOCKED:
case RTLD_LOCK_WLOCKED:
break;
case RTLD_LOCK_RLOCKED:
siglongjmp(state->env, 1);
break;
default:
assert(0);
}
}
void
lockdflt_init(void)
{
sigfillset(&fullsigmask);
sigdelset(&fullsigmask, SIGILL);
sigdelset(&fullsigmask, SIGTRAP);
sigdelset(&fullsigmask, SIGABRT);
sigdelset(&fullsigmask, SIGEMT);
sigdelset(&fullsigmask, SIGFPE);
sigdelset(&fullsigmask, SIGBUS);
sigdelset(&fullsigmask, SIGSEGV);
sigdelset(&fullsigmask, SIGSYS);
_rtld_thread_init(NULL);
}
void
_rtld_thread_init(void *dummy __unused)
{
lock_reset(rtld_phdr_lock);
lock_reset(rtld_bind_lock);
lock_reset(rtld_libc_lock);
}
static RtldLockState fork_states[3];
void
_rtld_thread_prefork(void)
{
wlock_acquire(rtld_phdr_lock, &fork_states[0]);
wlock_acquire(rtld_bind_lock, &fork_states[1]);
wlock_acquire(rtld_libc_lock, &fork_states[2]);
}
void
_rtld_thread_postfork(void)
{
lock_release(rtld_libc_lock, &fork_states[2]);
lock_release(rtld_bind_lock, &fork_states[1]);
lock_release(rtld_phdr_lock, &fork_states[0]);
}
void
_rtld_thread_childfork(void)
{
sigset_t tmp_oldsigmask;
lock_reset(rtld_phdr_lock);
lock_reset(rtld_bind_lock);
tmp_oldsigmask = rtld_libc_lock->savesigmask;
lock_reset(rtld_libc_lock);
sigprocmask(SIG_SETMASK, &tmp_oldsigmask, NULL);
}