/*- * Copyright (c) 2008 Yahoo!, Inc. * All rights reserved. * Written by: John Baldwin * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 *)(uintptr_t)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 *)(uintptr_t)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 *)(uintptr_t)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 *)(uintptr_t)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); }