diff options
Diffstat (limited to 'tools/regression/posixsem')
-rw-r--r-- | tools/regression/posixsem/Makefile | 11 | ||||
-rw-r--r-- | tools/regression/posixsem/posixsem.c | 1437 | ||||
-rw-r--r-- | tools/regression/posixsem/posixsem.t | 5 | ||||
-rw-r--r-- | tools/regression/posixsem/test.c | 128 | ||||
-rw-r--r-- | tools/regression/posixsem/test.h | 59 |
5 files changed, 1640 insertions, 0 deletions
diff --git a/tools/regression/posixsem/Makefile b/tools/regression/posixsem/Makefile new file mode 100644 index 000000000000..f7568f40781b --- /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 000000000000..465d6e7f26df --- /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 000000000000..198d2be6c89c --- /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 000000000000..8583a0dbbac6 --- /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 000000000000..505679b1fdb0 --- /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__ */ |