#include <err.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <thread.h>
#include <synch.h>
#include <stdbool.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <libproc.h>
#include <string.h>
#include <sys/debug.h>
typedef enum {
MUTEX_TEST_F_USE_ATTR = 1 << 0,
MUTEX_TEST_F_SET_TYPE = 1 << 1,
MUTEX_TEST_F_DEADLOCK = 1 << 2,
MUTEX_TEST_F_ILLUMOS = 1 << 3,
MUTEX_TEST_F_UNLOCK = 1 << 4
} mutex_test_flags_t;
typedef struct {
const char *mt_desc;
mutex_test_flags_t mt_flags;
int mt_type;
int mt_ret;
} mutex_test_t;
const mutex_test_t mutex_tests[] = {
{
.mt_desc = "pthread attr NULL",
.mt_flags = MUTEX_TEST_F_DEADLOCK,
.mt_ret = INT32_MIN
}, {
.mt_desc = "pthread attr unset",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_DEADLOCK,
.mt_ret = INT32_MIN
}, {
.mt_desc = "pthrad attr default",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
MUTEX_TEST_F_DEADLOCK,
.mt_type = PTHREAD_MUTEX_DEFAULT,
.mt_ret = INT32_MIN
}, {
.mt_desc = "pthread attr normal",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
MUTEX_TEST_F_DEADLOCK,
.mt_type = PTHREAD_MUTEX_NORMAL,
.mt_ret = INT32_MIN
}, {
.mt_desc = "pthread attr recursive",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
.mt_type = PTHREAD_MUTEX_RECURSIVE,
.mt_ret = 0
}, {
.mt_desc = "pthread attr errorcheck",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
.mt_type = PTHREAD_MUTEX_ERRORCHECK,
.mt_ret = EDEADLK
}, {
.mt_desc = "pthread attr recursive unlock",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
MUTEX_TEST_F_UNLOCK,
.mt_type = PTHREAD_MUTEX_RECURSIVE,
.mt_ret = EPERM
}, {
.mt_desc = "pthread attr errorcheck unlock",
.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
MUTEX_TEST_F_UNLOCK,
.mt_type = PTHREAD_MUTEX_ERRORCHECK,
.mt_ret = EPERM
}, {
.mt_desc = "illumos USYNC_THREAD",
.mt_flags = MUTEX_TEST_F_DEADLOCK | MUTEX_TEST_F_ILLUMOS,
.mt_type = USYNC_THREAD,
.mt_ret = INT32_MAX
}, {
.mt_desc = "illumos error check",
.mt_flags = MUTEX_TEST_F_ILLUMOS,
.mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
.mt_ret = EDEADLK
}, {
.mt_desc = "illumos recursive",
.mt_flags = MUTEX_TEST_F_ILLUMOS,
.mt_type = USYNC_THREAD | LOCK_RECURSIVE,
.mt_ret = 0
}, {
.mt_desc = "illumos recursive error check",
.mt_flags = MUTEX_TEST_F_ILLUMOS,
.mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
.mt_ret = 0
}, {
.mt_desc = "illumos error check unlock",
.mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
.mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
.mt_ret = EPERM
}, {
.mt_desc = "illumos recursive error check unlock",
.mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
.mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
.mt_ret = EPERM
}
};
static void *
mutex_test_thr(void *arg)
{
int ret;
pthread_mutexattr_t attr, *attrp = NULL;
const mutex_test_t *test = arg;
if ((test->mt_flags & MUTEX_TEST_F_USE_ATTR) != 0) {
VERIFY0(pthread_mutexattr_init(&attr));
attrp = &attr;
if ((test->mt_flags & MUTEX_TEST_F_SET_TYPE) != 0) {
VERIFY0(pthread_mutexattr_settype(&attr,
test->mt_type));
}
}
if ((test->mt_flags & MUTEX_TEST_F_UNLOCK) != 0) {
if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
mutex_t m;
VERIFY0(mutex_init(&m, test->mt_type, NULL));
ret = mutex_unlock(&m);
} else {
pthread_mutex_t pm;
VERIFY0(pthread_mutex_init(&pm, attrp));
ret = pthread_mutex_unlock(&pm);
}
return ((void *)(uintptr_t)ret);
}
if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
mutex_t m;
VERIFY0(mutex_init(&m, test->mt_type, NULL));
VERIFY0(mutex_lock(&m));
ret = mutex_lock(&m);
} else {
pthread_mutex_t pm;
VERIFY0(pthread_mutex_init(&pm, attrp));
VERIFY0(pthread_mutex_lock(&pm));
ret = pthread_mutex_lock(&pm);
}
return ((void *)(uintptr_t)ret);
}
typedef enum {
THR_STATE_PARKED,
THR_STATE_DEAD,
THR_STATE_RUNNING
} thr_state_t;
static thr_state_t
mutex_test_thr_state(thread_t thr)
{
lwpstatus_t lwp;
char name[SYS2STR_MAX];
if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) {
int e = errno;
switch (e) {
case ENOENT:
return (THR_STATE_DEAD);
default:
errc(EXIT_FAILURE, e, "fatal error: got unexpected "
"error while trying to get lwpstatus");
}
}
if ((lwp.pr_flags & PR_ASLEEP) == 0) {
return (THR_STATE_RUNNING);
}
if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == 0) {
return (THR_STATE_RUNNING);
}
if (strcmp(name, "lwp_park") == 0) {
return (THR_STATE_PARKED);
}
return (THR_STATE_RUNNING);
}
static bool
mutex_test_run_one(const mutex_test_t *test)
{
int err, lock;
thread_t thr;
thr_state_t state;
void *val;
err = thr_create(NULL, 0, mutex_test_thr, (void *)test, 0, &thr);
if (err != 0) {
errc(EXIT_FAILURE, err, "fatal test error: could not create "
"thread for %s", test->mt_desc);
}
while ((state = mutex_test_thr_state(thr)) == THR_STATE_RUNNING) {
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = MSEC2NSEC(10);
(void) nanosleep(&sleep, NULL);
}
if (state == THR_STATE_PARKED) {
if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
(void) printf("TEST PASSED: %s: successfully "
"deadlocked\n", test->mt_desc);
return (true);
}
(void) sleep(100000);
warnx("TEST FAILED: %s: thread deadlocked, but expected return "
"value %d", test->mt_desc, test->mt_ret);
return (false);
}
VERIFY0(thr_join(thr, NULL, &val));
lock = (int)(uintptr_t)val;
if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
warnx("TEST FAILED: %s: expected deadlock, but mutex lock "
"returned %d", test->mt_desc, lock);
return (false);
} else if (lock != test->mt_ret) {
warnx("TEST FAILED: %s: found return value %d, expected %d",
test->mt_desc, lock, test->mt_ret);
return (false);
} else {
(void) printf("TEST PASSED: %s: got correct lock return value "
"(%d)\n", test->mt_desc, test->mt_ret);
return (true);
}
}
int
main(void)
{
int ret = EXIT_SUCCESS;
if (getenv("_THREAD_ASYNC_SAFE") != NULL) {
errx(EXIT_FAILURE, "cannot run tests because "
"_THREAD_ASYNC_SAFE is set in the environment!");
}
for (size_t i = 0; i < ARRAY_SIZE(mutex_tests); i++) {
if (!mutex_test_run_one(&mutex_tests[i])) {
ret = EXIT_FAILURE;
}
}
exit(ret);
}