2 * Copyright (c) 2008 Yahoo!, Inc.
4 * Written by: John Baldwin <jhb@FreeBSD.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the names of any co-contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/cdefs.h>
33 #include <sys/param.h>
34 #include <sys/queue.h>
36 #include <sys/sysctl.h>
38 #include <sys/types.h>
39 #include <sys/types.h>
47 #include <semaphore.h>
58 /* Cut and pasted from kernel header, bah! */
60 /* Operations on timespecs */
61 #define timespecclear(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0)
62 #define timespecisset(tvp) ((tvp)->tv_sec || (tvp)->tv_nsec)
63 #define timespeccmp(tvp, uvp, cmp) \
64 (((tvp)->tv_sec == (uvp)->tv_sec) ? \
65 ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \
66 ((tvp)->tv_sec cmp (uvp)->tv_sec))
67 #define timespecadd(vvp, uvp) \
69 (vvp)->tv_sec += (uvp)->tv_sec; \
70 (vvp)->tv_nsec += (uvp)->tv_nsec; \
71 if ((vvp)->tv_nsec >= 1000000000) { \
73 (vvp)->tv_nsec -= 1000000000; \
76 #define timespecsub(vvp, uvp) \
78 (vvp)->tv_sec -= (uvp)->tv_sec; \
79 (vvp)->tv_nsec -= (uvp)->tv_nsec; \
80 if ((vvp)->tv_nsec < 0) { \
82 (vvp)->tv_nsec += 1000000000; \
87 #define TEST_PATH "/posixsem_regression_test"
89 #define ELAPSED(elapsed, limit) (abs((elapsed) - (limit)) < 100)
91 /* Macros for passing child status to parent over a pipe. */
92 #define CSTAT(class, error) ((class) << 16 | (error))
93 #define CSTAT_CLASS(stat) ((stat) >> 16)
94 #define CSTAT_ERROR(stat) ((stat) & 0xffff)
97 construct_shared_unnamed_sem(unsigned int count)
99 sem_t *id = mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE,
100 MAP_SHARED|MAP_ANON, -1, 0);
101 if (id == MAP_FAILED) {
106 if (sem_init(id, 1, count) < 0) {
107 fail_errno("sem_init");
108 munmap(id, sizeof(sem_t));
116 destruct_shared_unnamed_sem(sem_t *id)
118 if (sem_destroy(id) < 0)
119 fail_errno("sem_destroy");
121 if (munmap(id, sizeof(sem_t)) < 0)
122 fail_errno("munmap");
126 * Helper routine for tests that use a child process. This routine
127 * creates a pipe and forks a child process. The child process runs
128 * the 'func' routine which returns a status integer. The status
129 * integer gets written over the pipe to the parent and returned in
130 * '*stat'. If there is an error in pipe(), fork(), or wait() this
131 * returns -1 and fails the test.
134 child_worker(int (*func)(void *arg), void *arg, int *stat)
155 write(pfd[1], &cstat, sizeof(cstat));
159 if (read(pfd[0], stat, sizeof(*stat)) < 0) {
160 fail_errno("read(pipe)");
165 if (waitpid(pid, NULL, 0) < 0) {
177 * Attempt a sem_open() that should fail with an expected error of
181 sem_open_should_fail(const char *path, int flags, mode_t mode,
182 unsigned int value, int error)
186 id = sem_open(path, flags, mode, value);
187 if (id != SEM_FAILED) {
188 fail_err("sem_open() didn't fail");
192 if (errno != error) {
193 fail_errno("sem_open");
200 * Attempt a sem_unlink() that should fail with an expected error of
204 sem_unlink_should_fail(const char *path, int error)
207 if (sem_unlink(path) >= 0) {
208 fail_err("sem_unlink() didn't fail");
211 if (errno != error) {
212 fail_errno("sem_unlink");
219 * Attempt a sem_close() that should fail with an expected error of
223 sem_close_should_fail(sem_t *id, int error)
226 if (sem_close(id) >= 0) {
227 fail_err("sem_close() didn't fail");
230 if (errno != error) {
231 fail_errno("sem_close");
238 * Attempt a sem_init() that should fail with an expected error of
242 sem_init_should_fail(unsigned int value, int error)
246 if (sem_init(&id, 0, value) >= 0) {
247 fail_err("sem_init() didn't fail");
251 if (errno != error) {
252 fail_errno("sem_init");
259 * Attempt a sem_destroy() that should fail with an expected error of
263 sem_destroy_should_fail(sem_t *id, int error)
266 if (sem_destroy(id) >= 0) {
267 fail_err("sem_destroy() didn't fail");
270 if (errno != error) {
271 fail_errno("sem_destroy");
278 open_after_unlink(void)
282 id = sem_open(TEST_PATH, O_CREAT, 0777, 1);
283 if (id == SEM_FAILED) {
284 fail_errno("sem_open(1)");
289 if (sem_unlink(TEST_PATH) < 0) {
290 fail_errno("sem_unlink");
294 sem_open_should_fail(TEST_PATH, O_RDONLY, 0777, 1, ENOENT);
296 TEST(open_after_unlink, "open after unlink");
299 open_invalid_path(void)
302 sem_open_should_fail("blah", 0, 0777, 1, ENOENT);
304 TEST(open_invalid_path, "open invalid path");
307 open_extra_flags(void)
310 sem_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, 1, EINVAL);
312 TEST(open_extra_flags, "open with extra flags");
318 (void)sem_unlink(TEST_PATH);
320 sem_open_should_fail(TEST_PATH, O_CREAT, 0777, SEM_VALUE_MAX+1U, EINVAL);
322 TEST(open_bad_value, "open with invalid initial value");
325 open_path_too_long(void)
329 page = malloc(MAXPATHLEN + 1);
330 memset(page, 'a', MAXPATHLEN);
332 page[MAXPATHLEN] = '\0';
333 sem_open_should_fail(page, O_RDONLY, 0777, 1, ENAMETOOLONG);
336 TEST(open_path_too_long, "open pathname too long");
339 open_nonexisting_semaphore(void)
341 sem_open_should_fail("/notreallythere", 0, 0777, 1, ENOENT);
344 TEST(open_nonexisting_semaphore, "open nonexistent semaphore");
347 exclusive_create_existing_semaphore(void)
351 id = sem_open(TEST_PATH, O_CREAT, 0777, 1);
352 if (id == SEM_FAILED) {
353 fail_errno("sem_open(O_CREAT)");
358 sem_open_should_fail(TEST_PATH, O_CREAT | O_EXCL, 0777, 1, EEXIST);
360 sem_unlink(TEST_PATH);
362 TEST(exclusive_create_existing_semaphore, "O_EXCL of existing semaphore");
368 sem_init_should_fail(SEM_VALUE_MAX+1U, EINVAL);
370 TEST(init_bad_value, "init with invalid initial value");
373 unlink_path_too_long(void)
377 page = malloc(MAXPATHLEN + 1);
378 memset(page, 'a', MAXPATHLEN);
379 page[MAXPATHLEN] = '\0';
380 sem_unlink_should_fail(page, ENAMETOOLONG);
383 TEST(unlink_path_too_long, "unlink pathname too long");
386 destroy_named_semaphore(void)
390 id = sem_open(TEST_PATH, O_CREAT, 0777, 1);
391 if (id == SEM_FAILED) {
392 fail_errno("sem_open(O_CREAT)");
396 sem_destroy_should_fail(id, EINVAL);
399 sem_unlink(TEST_PATH);
401 TEST(destroy_named_semaphore, "destroy named semaphore");
404 close_unnamed_semaphore(void)
408 if (sem_init(&id, 0, 1) < 0) {
409 fail_errno("sem_init");
413 sem_close_should_fail(&id, EINVAL);
417 TEST(close_unnamed_semaphore, "close unnamed semaphore");
420 create_unnamed_semaphore(void)
424 if (sem_init(&id, 0, 1) < 0) {
425 fail_errno("sem_init");
429 if (sem_destroy(&id) < 0) {
430 fail_errno("sem_destroy");
435 TEST(create_unnamed_semaphore, "create unnamed semaphore");
438 open_named_semaphore(void)
442 id = sem_open(TEST_PATH, O_CREAT, 0777, 1);
443 if (id == SEM_FAILED) {
444 fail_errno("sem_open(O_CREAT)");
448 if (sem_close(id) < 0) {
449 fail_errno("sem_close");
453 if (sem_unlink(TEST_PATH) < 0) {
454 fail_errno("sem_unlink");
459 TEST(open_named_semaphore, "create named semaphore");
462 checkvalue(sem_t *id, int expected)
466 if (sem_getvalue(id, &val) < 0) {
467 fail_errno("sem_getvalue");
470 if (val != expected) {
471 fail_err("sem value should be %d instead of %d", expected, val);
482 if (sem_init(&id, 0, 1) < 0) {
483 fail_errno("sem_init");
486 if (checkvalue(&id, 1) < 0) {
490 if (sem_post(&id) < 0) {
491 fail_errno("sem_post");
495 if (checkvalue(&id, 2) < 0) {
499 if (sem_destroy(&id) < 0) {
500 fail_errno("sem_destroy");
505 TEST(post_test, "simple post");
508 use_after_unlink_test(void)
513 * Create named semaphore with value of 1 and then unlink it
514 * while still retaining the initial reference.
516 id = sem_open(TEST_PATH, O_CREAT | O_EXCL, 0777, 1);
517 if (id == SEM_FAILED) {
518 fail_errno("sem_open(O_CREAT | O_EXCL)");
521 if (sem_unlink(TEST_PATH) < 0) {
522 fail_errno("sem_unlink");
526 if (checkvalue(id, 1) < 0) {
531 /* Post the semaphore to set its value to 2. */
532 if (sem_post(id) < 0) {
533 fail_errno("sem_post");
537 if (checkvalue(id, 2) < 0) {
542 /* Wait on the semaphore which should set its value to 1. */
543 if (sem_wait(id) < 0) {
544 fail_errno("sem_wait");
548 if (checkvalue(id, 1) < 0) {
553 if (sem_close(id) < 0) {
554 fail_errno("sem_close");
559 TEST(use_after_unlink_test, "use named semaphore after unlink");
562 unlocked_trywait(void)
566 if (sem_init(&id, 0, 1) < 0) {
567 fail_errno("sem_init");
571 /* This should succeed and decrement the value to 0. */
572 if (sem_trywait(&id) < 0) {
573 fail_errno("sem_trywait()");
577 if (checkvalue(&id, 0) < 0) {
582 if (sem_destroy(&id) < 0) {
583 fail_errno("sem_destroy");
588 TEST(unlocked_trywait, "unlocked trywait");
595 if (sem_init(&id, 0, 0) < 0) {
596 fail_errno("sem_init");
600 /* This should fail with EAGAIN and leave the value at 0. */
601 if (sem_trywait(&id) >= 0) {
602 fail_err("sem_trywait() didn't fail");
606 if (errno != EAGAIN) {
607 fail_errno("wrong error from sem_trywait()");
611 if (checkvalue(&id, 0) < 0) {
616 if (sem_destroy(&id) < 0) {
617 fail_errno("sem_destroy");
622 TEST(locked_trywait, "locked trywait");
625 * Use a timer to post a specific semaphore after a timeout. A timer
626 * is scheduled via schedule_post(). check_alarm() must be called
627 * afterwards to clean up and check for errors.
629 static sem_t *alarm_id = SEM_FAILED;
630 static int alarm_errno;
631 static int alarm_handler_installed;
634 alarm_handler(int signo)
637 if (sem_post(alarm_id) < 0)
642 check_alarm(int just_clear)
646 bzero(&it, sizeof(it));
648 setitimer(ITIMER_REAL, &it, NULL);
650 alarm_id = SEM_FAILED;
653 if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
654 fail_errno("setitimer");
657 if (alarm_errno != 0 && !just_clear) {
659 fail_errno("sem_post() (via timeout)");
663 alarm_id = SEM_FAILED;
669 schedule_post(sem_t *id, u_int msec)
673 if (!alarm_handler_installed) {
674 if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
675 fail_errno("signal(SIGALRM)");
678 alarm_handler_installed = 1;
680 if (alarm_id != SEM_FAILED) {
681 fail_err("sem_post() already scheduled");
685 bzero(&it, sizeof(it));
686 it.it_value.tv_sec = msec / 1000;
687 it.it_value.tv_usec = (msec % 1000) * 1000;
688 if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
689 fail_errno("setitimer");
696 timedwait(sem_t *id, u_int msec, u_int *delta, int error)
698 struct timespec start, end;
700 if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
701 fail_errno("clock_gettime(CLOCK_REALTIME)");
704 end.tv_sec = msec / 1000;
705 end.tv_nsec = msec % 1000 * 1000000;
706 timespecadd(&end, &start);
707 if (sem_timedwait(id, &end) < 0) {
708 if (errno != error) {
709 fail_errno("sem_timedwait");
712 } else if (error != 0) {
713 fail_err("sem_timedwait() didn't fail");
716 if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
717 fail_errno("clock_gettime(CLOCK_REALTIME)");
720 timespecsub(&end, &start);
721 *delta = end.tv_nsec / 1000000;
722 *delta += end.tv_sec * 1000;
727 unlocked_timedwait(void)
732 if (sem_init(&id, 0, 1) < 0) {
733 fail_errno("sem_init");
737 /* This should succeed right away and set the value to 0. */
738 if (timedwait(&id, 5000, &elapsed, 0) < 0) {
742 if (!ELAPSED(elapsed, 0)) {
743 fail_err("sem_timedwait() of unlocked sem took %ums", elapsed);
747 if (checkvalue(&id, 0) < 0) {
752 if (sem_destroy(&id) < 0) {
753 fail_errno("sem_destroy");
758 TEST(unlocked_timedwait, "unlocked timedwait");
761 expired_timedwait(void)
766 if (sem_init(&id, 0, 0) < 0) {
767 fail_errno("sem_init");
771 /* This should fail with a timeout and leave the value at 0. */
772 if (timedwait(&id, 2500, &elapsed, ETIMEDOUT) < 0) {
776 if (!ELAPSED(elapsed, 2500)) {
777 fail_err("sem_timedwait() of locked sem took %ums instead of 2500ms",
782 if (checkvalue(&id, 0) < 0) {
787 if (sem_destroy(&id) < 0) {
788 fail_errno("sem_destroy");
793 TEST(expired_timedwait, "locked timedwait timeout");
796 locked_timedwait(void)
802 id = construct_shared_unnamed_sem(0);
803 if (id == SEM_FAILED) {
804 fail_err("construct sem");
813 destruct_shared_unnamed_sem(id);
822 if (timedwait(id, 2000, &elapsed, 0) < 0) {
823 destruct_shared_unnamed_sem(id);
826 if (!ELAPSED(elapsed, 1000)) {
827 fail_err("sem_timedwait() with delayed post took %ums instead of 1000ms",
829 destruct_shared_unnamed_sem(id);
833 destruct_shared_unnamed_sem(id);
837 TEST(locked_timedwait, "locked timedwait");
840 testwait(sem_t *id, u_int *delta)
842 struct timespec start, end;
844 if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
845 fail_errno("clock_gettime(CLOCK_REALTIME)");
848 if (sem_wait(id) < 0) {
849 fail_errno("sem_wait");
852 if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
853 fail_errno("clock_gettime(CLOCK_REALTIME)");
856 timespecsub(&end, &start);
857 *delta = end.tv_nsec / 1000000;
858 *delta += end.tv_sec * 1000;
868 if (sem_init(&id, 0, 1) < 0) {
869 fail_errno("sem_init");
873 /* This should succeed right away and set the value to 0. */
874 if (testwait(&id, &elapsed) < 0) {
878 if (!ELAPSED(elapsed, 0)) {
879 fail_err("sem_wait() of unlocked sem took %ums", elapsed);
883 if (checkvalue(&id, 0) < 0) {
888 if (sem_destroy(&id) < 0) {
889 fail_errno("sem_destroy");
894 TEST(unlocked_wait, "unlocked wait");
903 id = construct_shared_unnamed_sem(0);
910 destruct_shared_unnamed_sem(id);
919 if (testwait(id, &elapsed) < 0) {
920 destruct_shared_unnamed_sem(id);
923 if (!ELAPSED(elapsed, 1000)) {
924 fail_err("sem_wait() with delayed post took %ums instead of 1000ms",
926 destruct_shared_unnamed_sem(id);
930 destruct_shared_unnamed_sem(id);
934 TEST(locked_wait, "locked wait");
937 * Fork off a child process. The child will open the semaphore via
938 * the same name. The child will then block on the semaphore waiting
939 * for the parent to post it.
942 wait_twoproc_child(void *arg)
946 id = sem_open(TEST_PATH, 0, 0, 0);
947 if (id == SEM_FAILED)
948 return (CSTAT(1, errno));
949 if (sem_wait(id) < 0)
950 return (CSTAT(2, errno));
951 if (sem_close(id) < 0)
952 return (CSTAT(3, errno));
953 return (CSTAT(0, 0));
957 wait_twoproc_test(void)
962 id = sem_open(TEST_PATH, O_CREAT, 0777, 0);
963 if (id == SEM_FAILED) {
964 fail_errno("sem_open");
968 if (schedule_post(id, 500) < 0) {
970 sem_unlink(TEST_PATH);
974 if (child_worker(wait_twoproc_child, NULL, &stat) < 0) {
977 sem_unlink(TEST_PATH);
981 errno = CSTAT_ERROR(stat);
982 switch (CSTAT_CLASS(stat)) {
987 fail_errno("child sem_open()");
990 fail_errno("child sem_wait()");
993 fail_errno("child sem_close()");
996 fail_err("bad child state %#x", stat);
1002 sem_unlink(TEST_PATH);
1004 TEST(wait_twoproc_test, "two proc wait");
1012 if (sem_init(&id, 0, SEM_VALUE_MAX) < 0) {
1013 fail_errno("sem_init");
1016 if (sem_getvalue(&id, &val) < 0) {
1017 fail_errno("sem_getvalue");
1021 if (val != SEM_VALUE_MAX) {
1022 fail_err("value %d != SEM_VALUE_MAX");
1027 fail_err("value < 0");
1031 if (sem_destroy(&id) < 0) {
1032 fail_errno("sem_destroy");
1037 TEST(maxvalue_test, "get value of SEM_VALUE_MAX semaphore");
1046 id = sem_open(TEST_PATH, O_CREAT, 0777, 0);
1047 if (id == SEM_FAILED) {
1048 fail_errno("sem_open");
1052 error = stat("/var/run/sem", &sb);
1057 if ((sb.st_mode & ALLPERMS) != (S_IRWXU|S_IRWXG|S_IRWXO|S_ISTXT)) {
1058 fail_err("semaphore dir has incorrect mode: 0%o\n",
1059 (sb.st_mode & ALLPERMS));
1067 TEST(file_test, "check semaphore directory has correct mode bits");
1070 main(int argc, char *argv[])
1073 signal(SIGSYS, SIG_IGN);
1075 return (test_exit_value);