summaryrefslogtreecommitdiffstats
path: root/tools/regression/posixsem
diff options
context:
space:
mode:
Diffstat (limited to 'tools/regression/posixsem')
-rw-r--r--tools/regression/posixsem/Makefile11
-rw-r--r--tools/regression/posixsem/posixsem.c1437
-rw-r--r--tools/regression/posixsem/posixsem.t5
-rw-r--r--tools/regression/posixsem/test.c128
-rw-r--r--tools/regression/posixsem/test.h59
5 files changed, 1640 insertions, 0 deletions
diff --git a/tools/regression/posixsem/Makefile b/tools/regression/posixsem/Makefile
new file mode 100644
index 0000000..f7568f4
--- /dev/null
+++ b/tools/regression/posixsem/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= posixsem
+SRCS= posixsem.c test.c
+DPADD= ${LIBKVM}
+LDADD= -lkvm
+NO_MAN=
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/tools/regression/posixsem/posixsem.c b/tools/regression/posixsem/posixsem.c
new file mode 100644
index 0000000..465d6e7
--- /dev/null
+++ b/tools/regression/posixsem/posixsem.c
@@ -0,0 +1,1437 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/_semaphore.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "test.h"
+
+/* Cut and pasted from kernel header, bah! */
+
+/* Operations on timespecs */
+#define timespecclear(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0)
+#define timespecisset(tvp) ((tvp)->tv_sec || (tvp)->tv_nsec)
+#define timespeccmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? \
+ ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \
+ ((tvp)->tv_sec cmp (uvp)->tv_sec))
+#define timespecadd(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec += (uvp)->tv_sec; \
+ (vvp)->tv_nsec += (uvp)->tv_nsec; \
+ if ((vvp)->tv_nsec >= 1000000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_nsec -= 1000000000; \
+ } \
+ } while (0)
+#define timespecsub(vvp, uvp) \
+ do { \
+ (vvp)->tv_sec -= (uvp)->tv_sec; \
+ (vvp)->tv_nsec -= (uvp)->tv_nsec; \
+ if ((vvp)->tv_nsec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_nsec += 1000000000; \
+ } \
+ } while (0)
+
+
+#define TEST_PATH "/tmp/posixsem_regression_test"
+
+#define ELAPSED(elapsed, limit) (abs((elapsed) - (limit)) < 100)
+
+/* Macros for passing child status to parent over a pipe. */
+#define CSTAT(class, error) ((class) << 16 | (error))
+#define CSTAT_CLASS(stat) ((stat) >> 16)
+#define CSTAT_ERROR(stat) ((stat) & 0xffff)
+
+/*
+ * Helper routine for tests that use a child process. This routine
+ * creates a pipe and forks a child process. The child process runs
+ * the 'func' routine which returns a status integer. The status
+ * integer gets written over the pipe to the parent and returned in
+ * '*stat'. If there is an error in pipe(), fork(), or wait() this
+ * returns -1 and fails the test.
+ */
+static int
+child_worker(int (*func)(void *arg), void *arg, int *stat)
+{
+ pid_t pid;
+ int pfd[2], cstat;
+
+ if (pipe(pfd) < 0) {
+ fail_errno("pipe");
+ return (-1);
+ }
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* Error. */
+ fail_errno("fork");
+ close(pfd[0]);
+ close(pfd[1]);
+ return (-1);
+ case 0:
+ /* Child. */
+ cstat = func(arg);
+ write(pfd[1], &cstat, sizeof(cstat));
+ exit(0);
+ }
+
+ if (read(pfd[0], stat, sizeof(*stat)) < 0) {
+ fail_errno("read(pipe)");
+ close(pfd[0]);
+ close(pfd[1]);
+ return (-1);
+ }
+ if (waitpid(pid, NULL, 0) < 0) {
+ fail_errno("wait");
+ close(pfd[0]);
+ close(pfd[1]);
+ return (-1);
+ }
+ close(pfd[0]);
+ close(pfd[1]);
+ return (0);
+}
+
+/*
+ * Attempt a ksem_open() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_open_should_fail(const char *path, int flags, mode_t mode, unsigned int
+ value, int error)
+{
+ semid_t id;
+
+ if (ksem_open(&id, path, flags, mode, value) >= 0) {
+ fail_err("ksem_open() didn't fail");
+ ksem_close(id);
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_open");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a ksem_unlink() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_unlink_should_fail(const char *path, int error)
+{
+
+ if (ksem_unlink(path) >= 0) {
+ fail_err("ksem_unlink() didn't fail");
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_unlink");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a ksem_close() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_close_should_fail(semid_t id, int error)
+{
+
+ if (ksem_close(id) >= 0) {
+ fail_err("ksem_close() didn't fail");
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_close");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a ksem_init() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_init_should_fail(unsigned int value, int error)
+{
+ semid_t id;
+
+ if (ksem_init(&id, value) >= 0) {
+ fail_err("ksem_init() didn't fail");
+ ksem_destroy(id);
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_init");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a ksem_destroy() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_destroy_should_fail(semid_t id, int error)
+{
+
+ if (ksem_destroy(id) >= 0) {
+ fail_err("ksem_destroy() didn't fail");
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+
+/*
+ * Attempt a ksem_post() that should fail with an expected error of
+ * 'error'.
+ */
+static void
+ksem_post_should_fail(semid_t id, int error)
+{
+
+ if (ksem_post(id) >= 0) {
+ fail_err("ksem_post() didn't fail");
+ return;
+ }
+ if (errno != error) {
+ fail_errno("ksem_post");
+ return;
+ }
+ pass();
+}
+
+static void
+open_after_unlink(void)
+{
+ semid_t id;
+
+ if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
+ fail_errno("ksem_open(1)");
+ return;
+ }
+ ksem_close(id);
+
+ if (ksem_unlink(TEST_PATH) < 0) {
+ fail_errno("ksem_unlink");
+ return;
+ }
+
+ ksem_open_should_fail(TEST_PATH, O_RDONLY, 0777, 1, ENOENT);
+}
+TEST(open_after_unlink, "open after unlink");
+
+static void
+open_invalid_path(void)
+{
+
+ ksem_open_should_fail("blah", 0, 0777, 1, EINVAL);
+}
+TEST(open_invalid_path, "open invalid path");
+
+static void
+open_extra_flags(void)
+{
+
+ ksem_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, 1, EINVAL);
+}
+TEST(open_extra_flags, "open with extra flags");
+
+static void
+open_bad_value(void)
+{
+
+ (void)ksem_unlink(TEST_PATH);
+
+ ksem_open_should_fail(TEST_PATH, O_CREAT, 0777, UINT_MAX, EINVAL);
+}
+TEST(open_bad_value, "open with invalid initial value");
+
+static void
+open_bad_path_pointer(void)
+{
+
+ ksem_open_should_fail((char *)1024, O_RDONLY, 0777, 1, EFAULT);
+}
+TEST(open_bad_path_pointer, "open bad path pointer");
+
+static void
+open_path_too_long(void)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ ksem_open_should_fail(page, O_RDONLY, 0777, 1, ENAMETOOLONG);
+ free(page);
+}
+TEST(open_path_too_long, "open pathname too long");
+
+static void
+open_nonexisting_semaphore(void)
+{
+
+ ksem_open_should_fail("/notreallythere", 0, 0777, 1, ENOENT);
+}
+TEST(open_nonexisting_semaphore, "open nonexistent semaphore");
+
+static void
+exclusive_create_existing_semaphore(void)
+{
+ semid_t id;
+
+ if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
+ fail_errno("ksem_open(O_CREAT)");
+ return;
+ }
+ ksem_close(id);
+
+ ksem_open_should_fail(TEST_PATH, O_CREAT | O_EXCL, 0777, 1, EEXIST);
+
+ ksem_unlink(TEST_PATH);
+}
+TEST(exclusive_create_existing_semaphore, "O_EXCL of existing semaphore");
+
+static void
+init_bad_value(void)
+{
+
+ ksem_init_should_fail(UINT_MAX, EINVAL);
+}
+TEST(init_bad_value, "init with invalid initial value");
+
+static void
+unlink_bad_path_pointer(void)
+{
+
+ ksem_unlink_should_fail((char *)1024, EFAULT);
+}
+TEST(unlink_bad_path_pointer, "unlink bad path pointer");
+
+static void
+unlink_path_too_long(void)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ ksem_unlink_should_fail(page, ENAMETOOLONG);
+ free(page);
+}
+TEST(unlink_path_too_long, "unlink pathname too long");
+
+static void
+destroy_named_semaphore(void)
+{
+ semid_t id;
+
+ if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
+ fail_errno("ksem_open(O_CREAT)");
+ return;
+ }
+
+ ksem_destroy_should_fail(id, EINVAL);
+
+ ksem_close(id);
+ ksem_unlink(TEST_PATH);
+}
+TEST(destroy_named_semaphore, "destroy named semaphore");
+
+static void
+close_unnamed_semaphore(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ ksem_close_should_fail(id, EINVAL);
+
+ ksem_destroy(id);
+}
+TEST(close_unnamed_semaphore, "close unnamed semaphore");
+
+static void
+destroy_invalid_fd(void)
+{
+
+ ksem_destroy_should_fail(STDERR_FILENO, EINVAL);
+}
+TEST(destroy_invalid_fd, "destroy non-semaphore file descriptor");
+
+static void
+close_invalid_fd(void)
+{
+
+ ksem_close_should_fail(STDERR_FILENO, EINVAL);
+}
+TEST(close_invalid_fd, "close non-semaphore file descriptor");
+
+static void
+create_unnamed_semaphore(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(create_unnamed_semaphore, "create unnamed semaphore");
+
+static void
+open_named_semaphore(void)
+{
+ semid_t id;
+
+ if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
+ fail_errno("ksem_open(O_CREAT)");
+ return;
+ }
+
+ if (ksem_close(id) < 0) {
+ fail_errno("ksem_close");
+ return;
+ }
+
+ if (ksem_unlink(TEST_PATH) < 0) {
+ fail_errno("ksem_unlink");
+ return;
+ }
+ pass();
+}
+TEST(open_named_semaphore, "create named semaphore");
+
+static void
+getvalue_invalid_semaphore(void)
+{
+ int val;
+
+ if (ksem_getvalue(STDERR_FILENO, &val) >= 0) {
+ fail_err("ksem_getvalue() didn't fail");
+ return;
+ }
+ if (errno != EINVAL) {
+ fail_errno("ksem_getvalue");
+ return;
+ }
+ pass();
+}
+TEST(getvalue_invalid_semaphore, "get value of invalid semaphore");
+
+static void
+post_invalid_semaphore(void)
+{
+
+ ksem_post_should_fail(STDERR_FILENO, EINVAL);
+}
+TEST(post_invalid_semaphore, "post of invalid semaphore");
+
+static void
+wait_invalid_semaphore(void)
+{
+
+ if (ksem_wait(STDERR_FILENO) >= 0) {
+ fail_err("ksem_wait() didn't fail");
+ return;
+ }
+ if (errno != EINVAL) {
+ fail_errno("ksem_wait");
+ return;
+ }
+ pass();
+}
+TEST(wait_invalid_semaphore, "wait for invalid semaphore");
+
+static void
+trywait_invalid_semaphore(void)
+{
+
+ if (ksem_trywait(STDERR_FILENO) >= 0) {
+ fail_err("ksem_trywait() didn't fail");
+ return;
+ }
+ if (errno != EINVAL) {
+ fail_errno("ksem_trywait");
+ return;
+ }
+ pass();
+}
+TEST(trywait_invalid_semaphore, "try wait for invalid semaphore");
+
+static void
+timedwait_invalid_semaphore(void)
+{
+
+ if (ksem_timedwait(STDERR_FILENO, NULL) >= 0) {
+ fail_err("ksem_timedwait() didn't fail");
+ return;
+ }
+ if (errno != EINVAL) {
+ fail_errno("ksem_timedwait");
+ return;
+ }
+ pass();
+}
+TEST(timedwait_invalid_semaphore, "timed wait for invalid semaphore");
+
+static int
+checkvalue(semid_t id, int expected)
+{
+ int val;
+
+ if (ksem_getvalue(id, &val) < 0) {
+ fail_errno("ksem_getvalue");
+ return (-1);
+ }
+ if (val != expected) {
+ fail_err("sem value should be %d instead of %d", expected, val);
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+post_test(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+ if (checkvalue(id, 1) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (ksem_post(id) < 0) {
+ fail_errno("ksem_post");
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 2) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(post_test, "simple post");
+
+static void
+use_after_unlink_test(void)
+{
+ semid_t id;
+
+ /*
+ * Create named semaphore with value of 1 and then unlink it
+ * while still retaining the initial reference.
+ */
+ if (ksem_open(&id, TEST_PATH, O_CREAT | O_EXCL, 0777, 1) < 0) {
+ fail_errno("ksem_open(O_CREAT | O_EXCL)");
+ return;
+ }
+ if (ksem_unlink(TEST_PATH) < 0) {
+ fail_errno("ksem_unlink");
+ ksem_close(id);
+ return;
+ }
+ if (checkvalue(id, 1) < 0) {
+ ksem_close(id);
+ return;
+ }
+
+ /* Post the semaphore to set its value to 2. */
+ if (ksem_post(id) < 0) {
+ fail_errno("ksem_post");
+ ksem_close(id);
+ return;
+ }
+ if (checkvalue(id, 2) < 0) {
+ ksem_close(id);
+ return;
+ }
+
+ /* Wait on the semaphore which should set its value to 1. */
+ if (ksem_wait(id) < 0) {
+ fail_errno("ksem_wait");
+ ksem_close(id);
+ return;
+ }
+ if (checkvalue(id, 1) < 0) {
+ ksem_close(id);
+ return;
+ }
+
+ if (ksem_close(id) < 0) {
+ fail_errno("ksem_close");
+ return;
+ }
+ pass();
+}
+TEST(use_after_unlink_test, "use named semaphore after unlink");
+
+static void
+unlocked_trywait(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /* This should succeed and decrement the value to 0. */
+ if (ksem_trywait(id) < 0) {
+ fail_errno("ksem_trywait()");
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(unlocked_trywait, "unlocked trywait");
+
+static void
+locked_trywait(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, 0) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /* This should fail with EAGAIN and leave the value at 0. */
+ if (ksem_trywait(id) >= 0) {
+ fail_err("ksem_trywait() didn't fail");
+ ksem_destroy(id);
+ return;
+ }
+ if (errno != EAGAIN) {
+ fail_errno("wrong error from ksem_trywait()");
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(locked_trywait, "locked trywait");
+
+/*
+ * Use a timer to post a specific semaphore after a timeout. A timer
+ * is scheduled via schedule_post(). check_alarm() must be called
+ * afterwards to clean up and check for errors.
+ */
+static semid_t alarm_id = -1;
+static int alarm_errno;
+static int alarm_handler_installed;
+
+static void
+alarm_handler(int signo)
+{
+
+ if (ksem_post(alarm_id) < 0)
+ alarm_errno = errno;
+}
+
+static int
+check_alarm(int just_clear)
+{
+ struct itimerval it;
+
+ bzero(&it, sizeof(it));
+ if (just_clear) {
+ setitimer(ITIMER_REAL, &it, NULL);
+ alarm_errno = 0;
+ alarm_id = -1;
+ return (0);
+ }
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
+ fail_errno("setitimer");
+ return (-1);
+ }
+ if (alarm_errno != 0 && !just_clear) {
+ errno = alarm_errno;
+ fail_errno("ksem_post() (via timeout)");
+ alarm_errno = 0;
+ return (-1);
+ }
+ alarm_id = -1;
+
+ return (0);
+}
+
+static int
+schedule_post(semid_t id, u_int msec)
+{
+ struct itimerval it;
+
+ if (!alarm_handler_installed) {
+ if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
+ fail_errno("signal(SIGALRM)");
+ return (-1);
+ }
+ alarm_handler_installed = 1;
+ }
+ if (alarm_id != -1) {
+ fail_err("ksem_post() already scheduled");
+ return (-1);
+ }
+ alarm_id = id;
+ bzero(&it, sizeof(it));
+ it.it_value.tv_sec = msec / 1000;
+ it.it_value.tv_usec = (msec % 1000) * 1000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
+ fail_errno("setitimer");
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+timedwait(semid_t id, u_int msec, u_int *delta, int error)
+{
+ struct timespec start, end;
+
+ if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
+ fail_errno("clock_gettime(CLOCK_REALTIME)");
+ return (-1);
+ }
+ end.tv_sec = msec / 1000;
+ end.tv_nsec = msec % 1000 * 1000000;
+ timespecadd(&end, &start);
+ if (ksem_timedwait(id, &end) < 0) {
+ if (errno != error) {
+ fail_errno("ksem_timedwait");
+ return (-1);
+ }
+ } else if (error != 0) {
+ fail_err("ksem_timedwait() didn't fail");
+ return (-1);
+ }
+ if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
+ fail_errno("clock_gettime(CLOCK_REALTIME)");
+ return (-1);
+ }
+ timespecsub(&end, &start);
+ *delta = end.tv_nsec / 1000000;
+ *delta += end.tv_sec * 1000;
+ return (0);
+}
+
+static void
+unlocked_timedwait(void)
+{
+ semid_t id;
+ u_int elapsed;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /* This should succeed right away and set the value to 0. */
+ if (timedwait(id, 5000, &elapsed, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (!ELAPSED(elapsed, 0)) {
+ fail_err("ksem_timedwait() of unlocked sem took %ums", elapsed);
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(unlocked_timedwait, "unlocked timedwait");
+
+static void
+expired_timedwait(void)
+{
+ semid_t id;
+ u_int elapsed;
+
+ if (ksem_init(&id, 0) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /* This should fail with a timeout and leave the value at 0. */
+ if (timedwait(id, 2500, &elapsed, ETIMEDOUT) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (!ELAPSED(elapsed, 2500)) {
+ fail_err(
+ "ksem_timedwait() of locked sem took %ums instead of 2500ms",
+ elapsed);
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(expired_timedwait, "locked timedwait timeout");
+
+static void
+locked_timedwait(void)
+{
+ semid_t id;
+ u_int elapsed;
+
+ if (ksem_init(&id, 0) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /*
+ * Schedule a post to trigger after 1000 ms. The subsequent
+ * timedwait should succeed after 1000 ms as a result w/o
+ * timing out.
+ */
+ if (schedule_post(id, 1000) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (timedwait(id, 2000, &elapsed, 0) < 0) {
+ check_alarm(1);
+ ksem_destroy(id);
+ return;
+ }
+ if (!ELAPSED(elapsed, 1000)) {
+ fail_err(
+ "ksem_timedwait() with delayed post took %ums instead of 1000ms",
+ elapsed);
+ check_alarm(1);
+ ksem_destroy(id);
+ return;
+ }
+ if (check_alarm(0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(locked_timedwait, "locked timedwait");
+
+static int
+testwait(semid_t id, u_int *delta)
+{
+ struct timespec start, end;
+
+ if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
+ fail_errno("clock_gettime(CLOCK_REALTIME)");
+ return (-1);
+ }
+ if (ksem_wait(id) < 0) {
+ fail_errno("ksem_wait");
+ return (-1);
+ }
+ if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
+ fail_errno("clock_gettime(CLOCK_REALTIME)");
+ return (-1);
+ }
+ timespecsub(&end, &start);
+ *delta = end.tv_nsec / 1000000;
+ *delta += end.tv_sec * 1000;
+ return (0);
+}
+
+static void
+unlocked_wait(void)
+{
+ semid_t id;
+ u_int elapsed;
+
+ if (ksem_init(&id, 1) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /* This should succeed right away and set the value to 0. */
+ if (testwait(id, &elapsed) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (!ELAPSED(elapsed, 0)) {
+ fail_err("ksem_wait() of unlocked sem took %ums", elapsed);
+ ksem_destroy(id);
+ return;
+ }
+ if (checkvalue(id, 0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(unlocked_wait, "unlocked wait");
+
+static void
+locked_wait(void)
+{
+ semid_t id;
+ u_int elapsed;
+
+ if (ksem_init(&id, 0) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ /*
+ * Schedule a post to trigger after 1000 ms. The subsequent
+ * wait should succeed after 1000 ms as a result.
+ */
+ if (schedule_post(id, 1000) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+ if (testwait(id, &elapsed) < 0) {
+ check_alarm(1);
+ ksem_destroy(id);
+ return;
+ }
+ if (!ELAPSED(elapsed, 1000)) {
+ fail_err(
+ "ksem_wait() with delayed post took %ums instead of 1000ms",
+ elapsed);
+ check_alarm(1);
+ ksem_destroy(id);
+ return;
+ }
+ if (check_alarm(0) < 0) {
+ ksem_destroy(id);
+ return;
+ }
+
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(locked_wait, "locked wait");
+
+/*
+ * Fork off a child process. The child will open the semaphore via
+ * the same name. The child will then block on the semaphore waiting
+ * for the parent to post it.
+ */
+static int
+wait_twoproc_child(void *arg)
+{
+ semid_t id;
+
+ if (ksem_open(&id, TEST_PATH, 0, 0, 0) < 0)
+ return (CSTAT(1, errno));
+ if (ksem_wait(id) < 0)
+ return (CSTAT(2, errno));
+ if (ksem_close(id) < 0)
+ return (CSTAT(3, errno));
+ return (CSTAT(0, 0));
+}
+
+static void
+wait_twoproc_test(void)
+{
+ semid_t id;
+ int stat;
+
+ if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 0)) {
+ fail_errno("ksem_open");
+ return;
+ }
+
+ if (schedule_post(id, 500) < 0) {
+ ksem_close(id);
+ ksem_unlink(TEST_PATH);
+ return;
+ }
+
+ if (child_worker(wait_twoproc_child, NULL, &stat) < 0) {
+ check_alarm(1);
+ ksem_close(id);
+ ksem_unlink(TEST_PATH);
+ return;
+ }
+
+ errno = CSTAT_ERROR(stat);
+ switch (CSTAT_CLASS(stat)) {
+ case 0:
+ pass();
+ break;
+ case 1:
+ fail_errno("child ksem_open()");
+ break;
+ case 2:
+ fail_errno("child ksem_wait()");
+ break;
+ case 3:
+ fail_errno("child ksem_close()");
+ break;
+ default:
+ fail_err("bad child state %#x", stat);
+ break;
+ }
+
+ check_alarm(1);
+ ksem_close(id);
+ ksem_unlink(TEST_PATH);
+}
+TEST(wait_twoproc_test, "two proc wait");
+
+static void
+maxvalue_test(void)
+{
+ semid_t id;
+ int val;
+
+ if (ksem_init(&id, SEM_VALUE_MAX) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+ if (ksem_getvalue(id, &val) < 0) {
+ fail_errno("ksem_getvalue");
+ ksem_destroy(id);
+ return;
+ }
+ if (val != SEM_VALUE_MAX) {
+ fail_err("value %d != SEM_VALUE_MAX");
+ ksem_destroy(id);
+ return;
+ }
+ if (val < 0) {
+ fail_err("value < 0");
+ ksem_destroy(id);
+ return;
+ }
+ if (ksem_destroy(id) < 0) {
+ fail_errno("ksem_destroy");
+ return;
+ }
+ pass();
+}
+TEST(maxvalue_test, "get value of SEM_VALUE_MAX semaphore");
+
+static void
+maxvalue_post_test(void)
+{
+ semid_t id;
+
+ if (ksem_init(&id, SEM_VALUE_MAX) < 0) {
+ fail_errno("ksem_init");
+ return;
+ }
+
+ ksem_post_should_fail(id, EOVERFLOW);
+
+ ksem_destroy(id);
+}
+TEST(maxvalue_post_test, "post SEM_VALUE_MAX semaphore");
+
+static void
+busy_destroy_test(void)
+{
+ char errbuf[_POSIX2_LINE_MAX];
+ struct kinfo_proc *kp;
+ semid_t id;
+ pid_t pid;
+ kvm_t *kd;
+ int count;
+
+ kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, errbuf);
+ if (kd == NULL) {
+ fail_err("kvm_openfiles: %s", errbuf);
+ return;
+ }
+
+ if (ksem_init(&id, 0) < 0) {
+ fail_errno("ksem_init");
+ kvm_close(kd);
+ return;
+ }
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* Error. */
+ fail_errno("fork");
+ ksem_destroy(id);
+ kvm_close(kd);
+ return;
+ case 0:
+ /* Child. */
+ ksem_wait(id);
+ exit(0);
+ }
+
+ /*
+ * Wait for the child process to block on the semaphore. This
+ * is a bit gross.
+ */
+ for (;;) {
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &count);
+ if (kp == NULL) {
+ fail_err("kvm_getprocs: %s", kvm_geterr(kd));
+ kvm_close(kd);
+ ksem_destroy(id);
+ return;
+ }
+ if (kp->ki_stat == SSLEEP &&
+ (strcmp(kp->ki_wmesg, "sem") == 0 ||
+ strcmp(kp->ki_wmesg, "ksem") == 0))
+ break;
+ usleep(1000);
+ }
+ kvm_close(kd);
+
+ ksem_destroy_should_fail(id, EBUSY);
+
+ /* Cleanup. */
+ ksem_post(id);
+ waitpid(pid, NULL, 0);
+ ksem_destroy(id);
+}
+TEST(busy_destroy_test, "destroy unnamed semaphore with waiter");
+
+static int
+exhaust_unnamed_child(void *arg)
+{
+ semid_t id;
+ int i, max;
+
+ max = (intptr_t)arg;
+ for (i = 0; i < max + 1; i++) {
+ if (ksem_init(&id, 1) < 0) {
+ if (errno == ENOSPC)
+ return (CSTAT(0, 0));
+ return (CSTAT(1, errno));
+ }
+ }
+ return (CSTAT(2, 0));
+}
+
+static void
+exhaust_unnamed_sems(void)
+{
+ size_t len;
+ int nsems_max, stat;
+
+ len = sizeof(nsems_max);
+ if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) <
+ 0) {
+ fail_errno("sysctl(p1003_1b.sem_nsems_max)");
+ return;
+ }
+
+ if (child_worker(exhaust_unnamed_child, (void *)nsems_max, &stat))
+ return;
+ errno = CSTAT_ERROR(stat);
+ switch (CSTAT_CLASS(stat)) {
+ case 0:
+ pass();
+ break;
+ case 1:
+ fail_errno("ksem_init");
+ break;
+ case 2:
+ fail_err("Limit of %d semaphores not enforced", nsems_max);
+ break;
+ default:
+ fail_err("bad child state %#x", stat);
+ break;
+ }
+}
+TEST(exhaust_unnamed_sems, "exhaust unnamed semaphores (1)");
+
+static int
+exhaust_named_child(void *arg)
+{
+ char buffer[64];
+ semid_t id;
+ int i, max;
+
+ max = (intptr_t)arg;
+ for (i = 0; i < max + 1; i++) {
+ snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
+ if (ksem_open(&id, buffer, O_CREAT, 0777, 1) < 0) {
+ if (errno == ENOSPC || errno == EMFILE ||
+ errno == ENFILE)
+ return (CSTAT(0, 0));
+ return (CSTAT(1, errno));
+ }
+ }
+ return (CSTAT(2, errno));
+}
+
+static void
+exhaust_named_sems(void)
+{
+ char buffer[64];
+ size_t len;
+ int i, nsems_max, stat;
+
+ len = sizeof(nsems_max);
+ if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) <
+ 0) {
+ fail_errno("sysctl(p1003_1b.sem_nsems_max)");
+ return;
+ }
+
+ if (child_worker(exhaust_named_child, (void *)nsems_max, &stat) < 0)
+ return;
+ errno = CSTAT_ERROR(stat);
+ switch (CSTAT_CLASS(stat)) {
+ case 0:
+ pass();
+ break;
+ case 1:
+ fail_errno("ksem_open");
+ break;
+ case 2:
+ fail_err("Limit of %d semaphores not enforced", nsems_max);
+ break;
+ default:
+ fail_err("bad child state %#x", stat);
+ break;
+ }
+
+ /* Cleanup any semaphores created by the child. */
+ for (i = 0; i < nsems_max + 1; i++) {
+ snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
+ ksem_unlink(buffer);
+ }
+}
+TEST(exhaust_named_sems, "exhaust named semaphores (1)");
+
+static int
+fdlimit_set(void *arg)
+{
+ struct rlimit rlim;
+ int max;
+
+ max = (intptr_t)arg;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) < 0)
+ return (CSTAT(3, errno));
+ rlim.rlim_cur = max;
+ if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
+ return (CSTAT(4, errno));
+ return (0);
+}
+
+static int
+fdlimit_unnamed_child(void *arg)
+{
+ int stat;
+
+ stat = fdlimit_set(arg);
+ if (stat == 0)
+ stat = exhaust_unnamed_child(arg);
+ return (stat);
+}
+
+static void
+fdlimit_unnamed_sems(void)
+{
+ int nsems_max, stat;
+
+ nsems_max = 10;
+ if (child_worker(fdlimit_unnamed_child, (void *)nsems_max, &stat))
+ return;
+ errno = CSTAT_ERROR(stat);
+ switch (CSTAT_CLASS(stat)) {
+ case 0:
+ pass();
+ break;
+ case 1:
+ fail_errno("ksem_init");
+ break;
+ case 2:
+ fail_err("Limit of %d semaphores not enforced", nsems_max);
+ break;
+ case 3:
+ fail_errno("getrlimit");
+ break;
+ case 4:
+ fail_errno("getrlimit");
+ break;
+ default:
+ fail_err("bad child state %#x", stat);
+ break;
+ }
+}
+TEST(fdlimit_unnamed_sems, "exhaust unnamed semaphores (2)");
+
+static int
+fdlimit_named_child(void *arg)
+{
+ int stat;
+
+ stat = fdlimit_set(arg);
+ if (stat == 0)
+ stat = exhaust_named_child(arg);
+ return (stat);
+}
+
+static void
+fdlimit_named_sems(void)
+{
+ char buffer[64];
+ int i, nsems_max, stat;
+
+ nsems_max = 10;
+ if (child_worker(fdlimit_named_child, (void *)nsems_max, &stat) < 0)
+ return;
+ errno = CSTAT_ERROR(stat);
+ switch (CSTAT_CLASS(stat)) {
+ case 0:
+ pass();
+ break;
+ case 1:
+ fail_errno("ksem_open");
+ break;
+ case 2:
+ fail_err("Limit of %d semaphores not enforced", nsems_max);
+ break;
+ case 3:
+ fail_errno("getrlimit");
+ break;
+ case 4:
+ fail_errno("getrlimit");
+ break;
+ default:
+ fail_err("bad child state %#x", stat);
+ break;
+ }
+
+ /* Cleanup any semaphores created by the child. */
+ for (i = 0; i < nsems_max + 1; i++) {
+ snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
+ ksem_unlink(buffer);
+ }
+}
+TEST(fdlimit_named_sems, "exhaust named semaphores (2)");
+
+int
+main(int argc, char *argv[])
+{
+
+ signal(SIGSYS, SIG_IGN);
+ run_tests();
+ return (0);
+}
diff --git a/tools/regression/posixsem/posixsem.t b/tools/regression/posixsem/posixsem.t
new file mode 100644
index 0000000..198d2be
--- /dev/null
+++ b/tools/regression/posixsem/posixsem.t
@@ -0,0 +1,5 @@
+#!/bin/sh
+#
+# $FreeBSD$
+
+./posixsem
diff --git a/tools/regression/posixsem/test.c b/tools/regression/posixsem/test.c
new file mode 100644
index 0000000..8583a0d
--- /dev/null
+++ b/tools/regression/posixsem/test.c
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "test.h"
+
+static int test_index;
+static struct regression_test *test;
+static int test_acknowleged;
+
+SET_DECLARE(regression_tests_set, struct regression_test);
+
+/*
+ * Outputs a test summary of the following:
+ *
+ * <status> <test #> [name] [# <fmt> [fmt args]]
+ */
+static void
+vprint_status(const char *status, const char *fmt, va_list ap)
+{
+
+ printf("%s %d", status, test_index);
+ if (test->rt_name)
+ printf(" - %s", test->rt_name);
+ if (fmt) {
+ printf(" # ");
+ vprintf(fmt, ap);
+ }
+ printf("\n");
+ test_acknowleged = 1;
+}
+
+static void
+print_status(const char *status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vprint_status(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+pass(void)
+{
+
+ print_status("ok", NULL);
+}
+
+void
+fail(void)
+{
+
+ print_status("not ok", NULL);
+}
+
+void
+fail_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vprint_status("not ok", fmt, ap);
+ va_end(ap);
+}
+
+void
+skip(const char *reason)
+{
+
+ print_status("ok", "skip %s", reason);
+}
+
+void
+todo(const char *reason)
+{
+
+ print_status("not ok", "TODO %s", reason);
+}
+
+void
+run_tests(void)
+{
+ struct regression_test **testp;
+
+ printf("1..%td\n", SET_COUNT(regression_tests_set));
+ test_index = 1;
+ SET_FOREACH(testp, regression_tests_set) {
+ test_acknowleged = 0;
+ test = *testp;
+ test->rt_function();
+ if (!test_acknowleged)
+ print_status("not ok", "unknown status");
+ test_index++;
+ }
+}
diff --git a/tools/regression/posixsem/test.h b/tools/regression/posixsem/test.h
new file mode 100644
index 0000000..505679b
--- /dev/null
+++ b/tools/regression/posixsem/test.h
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __TEST_H__
+#define __TEST_H__
+
+#include <sys/linker_set.h>
+
+struct regression_test {
+ void (*rt_function)(void);
+ const char *rt_name;
+};
+
+#define TEST(function, name) \
+ static struct regression_test _regtest_##function = { \
+ (function), \
+ (name) \
+ }; \
+ DATA_SET(regression_tests_set, _regtest_##function)
+
+void fail(void);
+void fail_err(const char *fmt, ...);
+void pass(void);
+void run_tests(void);
+void skip(const char *reason);
+void todo(const char *reason);
+
+#define fail_errno(tag) fail_err("%s: %s", (tag), strerror(errno))
+
+#endif /* !__TEST_H__ */
OpenPOWER on IntegriCloud