xref: /freebsd/tools/regression/posixsem/posixsem.c (revision 6bfca4dcab07dad45a805879d954876b353c0810)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2008 Yahoo!, Inc.
5  * All rights reserved.
6  * Written by: John Baldwin <jhb@FreeBSD.org>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/queue.h>
35 #include <sys/_semaphore.h>
36 #include <sys/sysctl.h>
37 #include <sys/time.h>
38 #include <sys/user.h>
39 #include <sys/wait.h>
40 
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <kvm.h>
44 #include <limits.h>
45 #include <semaphore.h>
46 #include <signal.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <time.h>
51 #include <unistd.h>
52 
53 #include "test.h"
54 
55 #define	TEST_PATH	"/tmp/posixsem_regression_test"
56 
57 #define	ELAPSED(elapsed, limit)		(abs((elapsed) - (limit)) < 100)
58 
59 /* Macros for passing child status to parent over a pipe. */
60 #define	CSTAT(class, error)		((class) << 16 | (error))
61 #define	CSTAT_CLASS(stat)		((stat) >> 16)
62 #define	CSTAT_ERROR(stat)		((stat) & 0xffff)
63 
64 /*
65  * Helper routine for tests that use a child process.  This routine
66  * creates a pipe and forks a child process.  The child process runs
67  * the 'func' routine which returns a status integer.  The status
68  * integer gets written over the pipe to the parent and returned in
69  * '*stat'.  If there is an error in pipe(), fork(), or wait() this
70  * returns -1 and fails the test.
71  */
72 static int
child_worker(int (* func)(void * arg),void * arg,int * stat)73 child_worker(int (*func)(void *arg), void *arg, int *stat)
74 {
75 	pid_t pid;
76 	int pfd[2], cstat;
77 
78 	if (pipe(pfd) < 0) {
79 		fail_errno("pipe");
80 		return (-1);
81 	}
82 
83 	pid = fork();
84 	switch (pid) {
85 	case -1:
86 		/* Error. */
87 		fail_errno("fork");
88 		close(pfd[0]);
89 		close(pfd[1]);
90 		return (-1);
91 	case 0:
92 		/* Child. */
93 		cstat = func(arg);
94 		write(pfd[1], &cstat, sizeof(cstat));
95 		exit(0);
96 	}
97 
98 	if (read(pfd[0], stat, sizeof(*stat)) < 0) {
99 		fail_errno("read(pipe)");
100 		close(pfd[0]);
101 		close(pfd[1]);
102 		return (-1);
103 	}
104 	if (waitpid(pid, NULL, 0) < 0) {
105 		fail_errno("wait");
106 		close(pfd[0]);
107 		close(pfd[1]);
108 		return (-1);
109 	}
110 	close(pfd[0]);
111 	close(pfd[1]);
112 	return (0);
113 }
114 
115 /*
116  * Attempt a ksem_open() that should fail with an expected error of
117  * 'error'.
118  */
119 static void
ksem_open_should_fail(const char * path,int flags,mode_t mode,unsigned int value,int error)120 ksem_open_should_fail(const char *path, int flags, mode_t mode, unsigned int
121     value, int error)
122 {
123 	semid_t id;
124 
125 	if (ksem_open(&id, path, flags, mode, value) >= 0) {
126 		fail_err("ksem_open() didn't fail");
127 		ksem_close(id);
128 		return;
129 	}
130 	if (errno != error) {
131 		fail_errno("ksem_open");
132 		return;
133 	}
134 	pass();
135 }
136 
137 /*
138  * Attempt a ksem_unlink() that should fail with an expected error of
139  * 'error'.
140  */
141 static void
ksem_unlink_should_fail(const char * path,int error)142 ksem_unlink_should_fail(const char *path, int error)
143 {
144 
145 	if (ksem_unlink(path) >= 0) {
146 		fail_err("ksem_unlink() didn't fail");
147 		return;
148 	}
149 	if (errno != error) {
150 		fail_errno("ksem_unlink");
151 		return;
152 	}
153 	pass();
154 }
155 
156 /*
157  * Attempt a ksem_close() that should fail with an expected error of
158  * 'error'.
159  */
160 static void
ksem_close_should_fail(semid_t id,int error)161 ksem_close_should_fail(semid_t id, int error)
162 {
163 
164 	if (ksem_close(id) >= 0) {
165 		fail_err("ksem_close() didn't fail");
166 		return;
167 	}
168 	if (errno != error) {
169 		fail_errno("ksem_close");
170 		return;
171 	}
172 	pass();
173 }
174 
175 /*
176  * Attempt a ksem_init() that should fail with an expected error of
177  * 'error'.
178  */
179 static void
ksem_init_should_fail(unsigned int value,int error)180 ksem_init_should_fail(unsigned int value, int error)
181 {
182 	semid_t id;
183 
184 	if (ksem_init(&id, value) >= 0) {
185 		fail_err("ksem_init() didn't fail");
186 		ksem_destroy(id);
187 		return;
188 	}
189 	if (errno != error) {
190 		fail_errno("ksem_init");
191 		return;
192 	}
193 	pass();
194 }
195 
196 /*
197  * Attempt a ksem_destroy() that should fail with an expected error of
198  * 'error'.
199  */
200 static void
ksem_destroy_should_fail(semid_t id,int error)201 ksem_destroy_should_fail(semid_t id, int error)
202 {
203 
204 	if (ksem_destroy(id) >= 0) {
205 		fail_err("ksem_destroy() didn't fail");
206 		return;
207 	}
208 	if (errno != error) {
209 		fail_errno("ksem_destroy");
210 		return;
211 	}
212 	pass();
213 }
214 
215 /*
216  * Attempt a ksem_post() that should fail with an expected error of
217  * 'error'.
218  */
219 static void
ksem_post_should_fail(semid_t id,int error)220 ksem_post_should_fail(semid_t id, int error)
221 {
222 
223 	if (ksem_post(id) >= 0) {
224 		fail_err("ksem_post() didn't fail");
225 		return;
226 	}
227 	if (errno != error) {
228 		fail_errno("ksem_post");
229 		return;
230 	}
231 	pass();
232 }
233 
234 static void
open_after_unlink(void)235 open_after_unlink(void)
236 {
237 	semid_t id;
238 
239 	if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
240 		fail_errno("ksem_open(1)");
241 		return;
242 	}
243 	ksem_close(id);
244 
245 	if (ksem_unlink(TEST_PATH) < 0) {
246 		fail_errno("ksem_unlink");
247 		return;
248 	}
249 
250 	ksem_open_should_fail(TEST_PATH, O_RDONLY, 0777, 1, ENOENT);
251 }
252 TEST(open_after_unlink, "open after unlink");
253 
254 static void
open_invalid_path(void)255 open_invalid_path(void)
256 {
257 
258 	ksem_open_should_fail("blah", 0, 0777, 1, EINVAL);
259 }
260 TEST(open_invalid_path, "open invalid path");
261 
262 static void
open_extra_flags(void)263 open_extra_flags(void)
264 {
265 
266 	ksem_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, 1, EINVAL);
267 }
268 TEST(open_extra_flags, "open with extra flags");
269 
270 static void
open_bad_value(void)271 open_bad_value(void)
272 {
273 
274 	(void)ksem_unlink(TEST_PATH);
275 
276 	ksem_open_should_fail(TEST_PATH, O_CREAT, 0777, UINT_MAX, EINVAL);
277 }
278 TEST(open_bad_value, "open with invalid initial value");
279 
280 static void
open_bad_path_pointer(void)281 open_bad_path_pointer(void)
282 {
283 
284 	ksem_open_should_fail((char *)1024, O_RDONLY, 0777, 1, EFAULT);
285 }
286 TEST(open_bad_path_pointer, "open bad path pointer");
287 
288 static void
open_path_too_long(void)289 open_path_too_long(void)
290 {
291 	char *page;
292 
293 	page = malloc(MAXPATHLEN + 1);
294 	memset(page, 'a', MAXPATHLEN);
295 	page[MAXPATHLEN] = '\0';
296 	ksem_open_should_fail(page, O_RDONLY, 0777, 1, ENAMETOOLONG);
297 	free(page);
298 }
299 TEST(open_path_too_long, "open pathname too long");
300 
301 static void
open_nonexisting_semaphore(void)302 open_nonexisting_semaphore(void)
303 {
304 
305 	ksem_open_should_fail("/notreallythere", 0, 0777, 1, ENOENT);
306 }
307 TEST(open_nonexisting_semaphore, "open nonexistent semaphore");
308 
309 static void
exclusive_create_existing_semaphore(void)310 exclusive_create_existing_semaphore(void)
311 {
312 	semid_t id;
313 
314 	if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
315 		fail_errno("ksem_open(O_CREAT)");
316 		return;
317 	}
318 	ksem_close(id);
319 
320 	ksem_open_should_fail(TEST_PATH, O_CREAT | O_EXCL, 0777, 1, EEXIST);
321 
322 	ksem_unlink(TEST_PATH);
323 }
324 TEST(exclusive_create_existing_semaphore, "O_EXCL of existing semaphore");
325 
326 static void
init_bad_value(void)327 init_bad_value(void)
328 {
329 
330 	ksem_init_should_fail(UINT_MAX, EINVAL);
331 }
332 TEST(init_bad_value, "init with invalid initial value");
333 
334 static void
unlink_bad_path_pointer(void)335 unlink_bad_path_pointer(void)
336 {
337 
338 	ksem_unlink_should_fail((char *)1024, EFAULT);
339 }
340 TEST(unlink_bad_path_pointer, "unlink bad path pointer");
341 
342 static void
unlink_path_too_long(void)343 unlink_path_too_long(void)
344 {
345 	char *page;
346 
347 	page = malloc(MAXPATHLEN + 1);
348 	memset(page, 'a', MAXPATHLEN);
349 	page[MAXPATHLEN] = '\0';
350 	ksem_unlink_should_fail(page, ENAMETOOLONG);
351 	free(page);
352 }
353 TEST(unlink_path_too_long, "unlink pathname too long");
354 
355 static void
destroy_named_semaphore(void)356 destroy_named_semaphore(void)
357 {
358 	semid_t id;
359 
360 	if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
361 		fail_errno("ksem_open(O_CREAT)");
362 		return;
363 	}
364 
365 	ksem_destroy_should_fail(id, EINVAL);
366 
367 	ksem_close(id);
368 	ksem_unlink(TEST_PATH);
369 }
370 TEST(destroy_named_semaphore, "destroy named semaphore");
371 
372 static void
close_unnamed_semaphore(void)373 close_unnamed_semaphore(void)
374 {
375 	semid_t id;
376 
377 	if (ksem_init(&id, 1) < 0) {
378 		fail_errno("ksem_init");
379 		return;
380 	}
381 
382 	ksem_close_should_fail(id, EINVAL);
383 
384 	ksem_destroy(id);
385 }
386 TEST(close_unnamed_semaphore, "close unnamed semaphore");
387 
388 static void
destroy_invalid_fd(void)389 destroy_invalid_fd(void)
390 {
391 
392 	ksem_destroy_should_fail(STDERR_FILENO, EINVAL);
393 }
394 TEST(destroy_invalid_fd, "destroy non-semaphore file descriptor");
395 
396 static void
close_invalid_fd(void)397 close_invalid_fd(void)
398 {
399 
400 	ksem_close_should_fail(STDERR_FILENO, EINVAL);
401 }
402 TEST(close_invalid_fd, "close non-semaphore file descriptor");
403 
404 static void
create_unnamed_semaphore(void)405 create_unnamed_semaphore(void)
406 {
407 	semid_t id;
408 
409 	if (ksem_init(&id, 1) < 0) {
410 		fail_errno("ksem_init");
411 		return;
412 	}
413 
414 	if (ksem_destroy(id) < 0) {
415 		fail_errno("ksem_destroy");
416 		return;
417 	}
418 	pass();
419 }
420 TEST(create_unnamed_semaphore, "create unnamed semaphore");
421 
422 static void
open_named_semaphore(void)423 open_named_semaphore(void)
424 {
425 	semid_t id;
426 
427 	if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) {
428 		fail_errno("ksem_open(O_CREAT)");
429 		return;
430 	}
431 
432 	if (ksem_close(id) < 0) {
433 		fail_errno("ksem_close");
434 		return;
435 	}
436 
437 	if (ksem_unlink(TEST_PATH) < 0) {
438 		fail_errno("ksem_unlink");
439 		return;
440 	}
441 	pass();
442 }
443 TEST(open_named_semaphore, "create named semaphore");
444 
445 static void
getvalue_invalid_semaphore(void)446 getvalue_invalid_semaphore(void)
447 {
448 	int val;
449 
450 	if (ksem_getvalue(STDERR_FILENO, &val) >= 0) {
451 		fail_err("ksem_getvalue() didn't fail");
452 		return;
453 	}
454 	if (errno != EINVAL) {
455 		fail_errno("ksem_getvalue");
456 		return;
457 	}
458 	pass();
459 }
460 TEST(getvalue_invalid_semaphore, "get value of invalid semaphore");
461 
462 static void
post_invalid_semaphore(void)463 post_invalid_semaphore(void)
464 {
465 
466 	ksem_post_should_fail(STDERR_FILENO, EINVAL);
467 }
468 TEST(post_invalid_semaphore, "post of invalid semaphore");
469 
470 static void
wait_invalid_semaphore(void)471 wait_invalid_semaphore(void)
472 {
473 
474 	if (ksem_wait(STDERR_FILENO) >= 0) {
475 		fail_err("ksem_wait() didn't fail");
476 		return;
477 	}
478 	if (errno != EINVAL) {
479 		fail_errno("ksem_wait");
480 		return;
481 	}
482 	pass();
483 }
484 TEST(wait_invalid_semaphore, "wait for invalid semaphore");
485 
486 static void
trywait_invalid_semaphore(void)487 trywait_invalid_semaphore(void)
488 {
489 
490 	if (ksem_trywait(STDERR_FILENO) >= 0) {
491 		fail_err("ksem_trywait() didn't fail");
492 		return;
493 	}
494 	if (errno != EINVAL) {
495 		fail_errno("ksem_trywait");
496 		return;
497 	}
498 	pass();
499 }
500 TEST(trywait_invalid_semaphore, "try wait for invalid semaphore");
501 
502 static void
timedwait_invalid_semaphore(void)503 timedwait_invalid_semaphore(void)
504 {
505 
506 	if (ksem_timedwait(STDERR_FILENO, NULL) >= 0) {
507 		fail_err("ksem_timedwait() didn't fail");
508 		return;
509 	}
510 	if (errno != EINVAL) {
511 		fail_errno("ksem_timedwait");
512 		return;
513 	}
514 	pass();
515 }
516 TEST(timedwait_invalid_semaphore, "timed wait for invalid semaphore");
517 
518 static int
checkvalue(semid_t id,int expected)519 checkvalue(semid_t id, int expected)
520 {
521 	int val;
522 
523 	if (ksem_getvalue(id, &val) < 0) {
524 		fail_errno("ksem_getvalue");
525 		return (-1);
526 	}
527 	if (val != expected) {
528 		fail_err("sem value should be %d instead of %d", expected, val);
529 		return (-1);
530 	}
531 	return (0);
532 }
533 
534 static void
post_test(void)535 post_test(void)
536 {
537 	semid_t id;
538 
539 	if (ksem_init(&id, 1) < 0) {
540 		fail_errno("ksem_init");
541 		return;
542 	}
543 	if (checkvalue(id, 1) < 0) {
544 		ksem_destroy(id);
545 		return;
546 	}
547 	if (ksem_post(id) < 0) {
548 		fail_errno("ksem_post");
549 		ksem_destroy(id);
550 		return;
551 	}
552 	if (checkvalue(id, 2) < 0) {
553 		ksem_destroy(id);
554 		return;
555 	}
556 	if (ksem_destroy(id) < 0) {
557 		fail_errno("ksem_destroy");
558 		return;
559 	}
560 	pass();
561 }
562 TEST(post_test, "simple post");
563 
564 static void
use_after_unlink_test(void)565 use_after_unlink_test(void)
566 {
567 	semid_t id;
568 
569 	/*
570 	 * Create named semaphore with value of 1 and then unlink it
571 	 * while still retaining the initial reference.
572 	 */
573 	if (ksem_open(&id, TEST_PATH, O_CREAT | O_EXCL, 0777, 1) < 0) {
574 		fail_errno("ksem_open(O_CREAT | O_EXCL)");
575 		return;
576 	}
577 	if (ksem_unlink(TEST_PATH) < 0) {
578 		fail_errno("ksem_unlink");
579 		ksem_close(id);
580 		return;
581 	}
582 	if (checkvalue(id, 1) < 0) {
583 		ksem_close(id);
584 		return;
585 	}
586 
587 	/* Post the semaphore to set its value to 2. */
588 	if (ksem_post(id) < 0) {
589 		fail_errno("ksem_post");
590 		ksem_close(id);
591 		return;
592 	}
593 	if (checkvalue(id, 2) < 0) {
594 		ksem_close(id);
595 		return;
596 	}
597 
598 	/* Wait on the semaphore which should set its value to 1. */
599 	if (ksem_wait(id) < 0) {
600 		fail_errno("ksem_wait");
601 		ksem_close(id);
602 		return;
603 	}
604 	if (checkvalue(id, 1) < 0) {
605 		ksem_close(id);
606 		return;
607 	}
608 
609 	if (ksem_close(id) < 0) {
610 		fail_errno("ksem_close");
611 		return;
612 	}
613 	pass();
614 }
615 TEST(use_after_unlink_test, "use named semaphore after unlink");
616 
617 static void
unlocked_trywait(void)618 unlocked_trywait(void)
619 {
620 	semid_t id;
621 
622 	if (ksem_init(&id, 1) < 0) {
623 		fail_errno("ksem_init");
624 		return;
625 	}
626 
627 	/* This should succeed and decrement the value to 0. */
628 	if (ksem_trywait(id) < 0) {
629 		fail_errno("ksem_trywait()");
630 		ksem_destroy(id);
631 		return;
632 	}
633 	if (checkvalue(id, 0) < 0) {
634 		ksem_destroy(id);
635 		return;
636 	}
637 
638 	if (ksem_destroy(id) < 0) {
639 		fail_errno("ksem_destroy");
640 		return;
641 	}
642 	pass();
643 }
644 TEST(unlocked_trywait, "unlocked trywait");
645 
646 static void
locked_trywait(void)647 locked_trywait(void)
648 {
649 	semid_t id;
650 
651 	if (ksem_init(&id, 0) < 0) {
652 		fail_errno("ksem_init");
653 		return;
654 	}
655 
656 	/* This should fail with EAGAIN and leave the value at 0. */
657 	if (ksem_trywait(id) >= 0) {
658 		fail_err("ksem_trywait() didn't fail");
659 		ksem_destroy(id);
660 		return;
661 	}
662 	if (errno != EAGAIN) {
663 		fail_errno("wrong error from ksem_trywait()");
664 		ksem_destroy(id);
665 		return;
666 	}
667 	if (checkvalue(id, 0) < 0) {
668 		ksem_destroy(id);
669 		return;
670 	}
671 
672 	if (ksem_destroy(id) < 0) {
673 		fail_errno("ksem_destroy");
674 		return;
675 	}
676 	pass();
677 }
678 TEST(locked_trywait, "locked trywait");
679 
680 /*
681  * Use a timer to post a specific semaphore after a timeout.  A timer
682  * is scheduled via schedule_post().  check_alarm() must be called
683  * afterwards to clean up and check for errors.
684  */
685 static semid_t alarm_id = -1;
686 static int alarm_errno;
687 static int alarm_handler_installed;
688 
689 static void
alarm_handler(int signo)690 alarm_handler(int signo)
691 {
692 
693 	if (ksem_post(alarm_id) < 0)
694 		alarm_errno = errno;
695 }
696 
697 static int
check_alarm(int just_clear)698 check_alarm(int just_clear)
699 {
700 	struct itimerval it;
701 
702 	bzero(&it, sizeof(it));
703 	if (just_clear) {
704 		setitimer(ITIMER_REAL, &it, NULL);
705 		alarm_errno = 0;
706 		alarm_id = -1;
707 		return (0);
708 	}
709 	if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
710 		fail_errno("setitimer");
711 		return (-1);
712 	}
713 	if (alarm_errno != 0 && !just_clear) {
714 		errno = alarm_errno;
715 		fail_errno("ksem_post() (via timeout)");
716 		alarm_errno = 0;
717 		return (-1);
718 	}
719 	alarm_id = -1;
720 
721 	return (0);
722 }
723 
724 static int
schedule_post(semid_t id,u_int msec)725 schedule_post(semid_t id, u_int msec)
726 {
727 	struct itimerval it;
728 
729 	if (!alarm_handler_installed) {
730 		if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
731 			fail_errno("signal(SIGALRM)");
732 			return (-1);
733 		}
734 		alarm_handler_installed = 1;
735 	}
736 	if (alarm_id != -1) {
737 		fail_err("ksem_post() already scheduled");
738 		return (-1);
739 	}
740 	alarm_id = id;
741 	bzero(&it, sizeof(it));
742 	it.it_value.tv_sec = msec / 1000;
743 	it.it_value.tv_usec = (msec % 1000) * 1000;
744 	if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
745 		fail_errno("setitimer");
746 		return (-1);
747 	}
748 	return (0);
749 }
750 
751 static int
timedwait(semid_t id,u_int msec,u_int * delta,int error)752 timedwait(semid_t id, u_int msec, u_int *delta, int error)
753 {
754 	struct timespec start, end;
755 
756 	if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
757 		fail_errno("clock_gettime(CLOCK_REALTIME)");
758 		return (-1);
759 	}
760 	end.tv_sec = msec / 1000;
761 	end.tv_nsec = msec % 1000 * 1000000;
762 	timespecadd(&end, &start, &end);
763 	if (ksem_timedwait(id, &end) < 0) {
764 		if (errno != error) {
765 			fail_errno("ksem_timedwait");
766 			return (-1);
767 		}
768 	} else if (error != 0) {
769 		fail_err("ksem_timedwait() didn't fail");
770 		return (-1);
771 	}
772 	if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
773 		fail_errno("clock_gettime(CLOCK_REALTIME)");
774 		return (-1);
775 	}
776 	timespecsub(&end, &start, &end);
777 	*delta = end.tv_nsec / 1000000;
778 	*delta += end.tv_sec * 1000;
779 	return (0);
780 }
781 
782 static void
unlocked_timedwait(void)783 unlocked_timedwait(void)
784 {
785 	semid_t id;
786 	u_int elapsed;
787 
788 	if (ksem_init(&id, 1) < 0) {
789 		fail_errno("ksem_init");
790 		return;
791 	}
792 
793 	/* This should succeed right away and set the value to 0. */
794 	if (timedwait(id, 5000, &elapsed, 0) < 0) {
795 		ksem_destroy(id);
796 		return;
797 	}
798 	if (!ELAPSED(elapsed, 0)) {
799 		fail_err("ksem_timedwait() of unlocked sem took %ums", elapsed);
800 		ksem_destroy(id);
801 		return;
802 	}
803 	if (checkvalue(id, 0) < 0) {
804 		ksem_destroy(id);
805 		return;
806 	}
807 
808 	if (ksem_destroy(id) < 0) {
809 		fail_errno("ksem_destroy");
810 		return;
811 	}
812 	pass();
813 }
814 TEST(unlocked_timedwait, "unlocked timedwait");
815 
816 static void
expired_timedwait(void)817 expired_timedwait(void)
818 {
819 	semid_t id;
820 	u_int elapsed;
821 
822 	if (ksem_init(&id, 0) < 0) {
823 		fail_errno("ksem_init");
824 		return;
825 	}
826 
827 	/* This should fail with a timeout and leave the value at 0. */
828 	if (timedwait(id, 2500, &elapsed, ETIMEDOUT) < 0) {
829 		ksem_destroy(id);
830 		return;
831 	}
832 	if (!ELAPSED(elapsed, 2500)) {
833 		fail_err(
834 	    "ksem_timedwait() of locked sem took %ums instead of 2500ms",
835 		    elapsed);
836 		ksem_destroy(id);
837 		return;
838 	}
839 	if (checkvalue(id, 0) < 0) {
840 		ksem_destroy(id);
841 		return;
842 	}
843 
844 	if (ksem_destroy(id) < 0) {
845 		fail_errno("ksem_destroy");
846 		return;
847 	}
848 	pass();
849 }
850 TEST(expired_timedwait, "locked timedwait timeout");
851 
852 static void
locked_timedwait(void)853 locked_timedwait(void)
854 {
855 	semid_t id;
856 	u_int elapsed;
857 
858 	if (ksem_init(&id, 0) < 0) {
859 		fail_errno("ksem_init");
860 		return;
861 	}
862 
863 	/*
864 	 * Schedule a post to trigger after 1000 ms.  The subsequent
865 	 * timedwait should succeed after 1000 ms as a result w/o
866 	 * timing out.
867 	 */
868 	if (schedule_post(id, 1000) < 0) {
869 		ksem_destroy(id);
870 		return;
871 	}
872 	if (timedwait(id, 2000, &elapsed, 0) < 0) {
873 		check_alarm(1);
874 		ksem_destroy(id);
875 		return;
876 	}
877 	if (!ELAPSED(elapsed, 1000)) {
878 		fail_err(
879 	    "ksem_timedwait() with delayed post took %ums instead of 1000ms",
880 		    elapsed);
881 		check_alarm(1);
882 		ksem_destroy(id);
883 		return;
884 	}
885 	if (check_alarm(0) < 0) {
886 		ksem_destroy(id);
887 		return;
888 	}
889 
890 	if (ksem_destroy(id) < 0) {
891 		fail_errno("ksem_destroy");
892 		return;
893 	}
894 	pass();
895 }
896 TEST(locked_timedwait, "locked timedwait");
897 
898 static int
testwait(semid_t id,u_int * delta)899 testwait(semid_t id, u_int *delta)
900 {
901 	struct timespec start, end;
902 
903 	if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
904 		fail_errno("clock_gettime(CLOCK_REALTIME)");
905 		return (-1);
906 	}
907 	if (ksem_wait(id) < 0) {
908 		fail_errno("ksem_wait");
909 		return (-1);
910 	}
911 	if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
912 		fail_errno("clock_gettime(CLOCK_REALTIME)");
913 		return (-1);
914 	}
915 	timespecsub(&end, &start, &end);
916 	*delta = end.tv_nsec / 1000000;
917 	*delta += end.tv_sec * 1000;
918 	return (0);
919 }
920 
921 static void
unlocked_wait(void)922 unlocked_wait(void)
923 {
924 	semid_t id;
925 	u_int elapsed;
926 
927 	if (ksem_init(&id, 1) < 0) {
928 		fail_errno("ksem_init");
929 		return;
930 	}
931 
932 	/* This should succeed right away and set the value to 0. */
933 	if (testwait(id, &elapsed) < 0) {
934 		ksem_destroy(id);
935 		return;
936 	}
937 	if (!ELAPSED(elapsed, 0)) {
938 		fail_err("ksem_wait() of unlocked sem took %ums", elapsed);
939 		ksem_destroy(id);
940 		return;
941 	}
942 	if (checkvalue(id, 0) < 0) {
943 		ksem_destroy(id);
944 		return;
945 	}
946 
947 	if (ksem_destroy(id) < 0) {
948 		fail_errno("ksem_destroy");
949 		return;
950 	}
951 	pass();
952 }
953 TEST(unlocked_wait, "unlocked wait");
954 
955 static void
locked_wait(void)956 locked_wait(void)
957 {
958 	semid_t id;
959 	u_int elapsed;
960 
961 	if (ksem_init(&id, 0) < 0) {
962 		fail_errno("ksem_init");
963 		return;
964 	}
965 
966 	/*
967 	 * Schedule a post to trigger after 1000 ms.  The subsequent
968 	 * wait should succeed after 1000 ms as a result.
969 	 */
970 	if (schedule_post(id, 1000) < 0) {
971 		ksem_destroy(id);
972 		return;
973 	}
974 	if (testwait(id, &elapsed) < 0) {
975 		check_alarm(1);
976 		ksem_destroy(id);
977 		return;
978 	}
979 	if (!ELAPSED(elapsed, 1000)) {
980 		fail_err(
981 	    "ksem_wait() with delayed post took %ums instead of 1000ms",
982 		    elapsed);
983 		check_alarm(1);
984 		ksem_destroy(id);
985 		return;
986 	}
987 	if (check_alarm(0) < 0) {
988 		ksem_destroy(id);
989 		return;
990 	}
991 
992 	if (ksem_destroy(id) < 0) {
993 		fail_errno("ksem_destroy");
994 		return;
995 	}
996 	pass();
997 }
998 TEST(locked_wait, "locked wait");
999 
1000 /*
1001  * Fork off a child process.  The child will open the semaphore via
1002  * the same name.  The child will then block on the semaphore waiting
1003  * for the parent to post it.
1004  */
1005 static int
wait_twoproc_child(void * arg)1006 wait_twoproc_child(void *arg)
1007 {
1008 	semid_t id;
1009 
1010 	if (ksem_open(&id, TEST_PATH, 0, 0, 0) < 0)
1011 		return (CSTAT(1, errno));
1012 	if (ksem_wait(id) < 0)
1013 		return (CSTAT(2, errno));
1014 	if (ksem_close(id) < 0)
1015 		return (CSTAT(3, errno));
1016 	return (CSTAT(0, 0));
1017 }
1018 
1019 static void
wait_twoproc_test(void)1020 wait_twoproc_test(void)
1021 {
1022 	semid_t id;
1023 	int stat;
1024 
1025 	if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 0)) {
1026 		fail_errno("ksem_open");
1027 		return;
1028 	}
1029 
1030 	if (schedule_post(id, 500) < 0) {
1031 		ksem_close(id);
1032 		ksem_unlink(TEST_PATH);
1033 		return;
1034 	}
1035 
1036 	if (child_worker(wait_twoproc_child, NULL, &stat) < 0) {
1037 		check_alarm(1);
1038 		ksem_close(id);
1039 		ksem_unlink(TEST_PATH);
1040 		return;
1041 	}
1042 
1043 	errno = CSTAT_ERROR(stat);
1044 	switch (CSTAT_CLASS(stat)) {
1045 	case 0:
1046 		pass();
1047 		break;
1048 	case 1:
1049 		fail_errno("child ksem_open()");
1050 		break;
1051 	case 2:
1052 		fail_errno("child ksem_wait()");
1053 		break;
1054 	case 3:
1055 		fail_errno("child ksem_close()");
1056 		break;
1057 	default:
1058 		fail_err("bad child state %#x", stat);
1059 		break;
1060 	}
1061 
1062 	check_alarm(1);
1063 	ksem_close(id);
1064 	ksem_unlink(TEST_PATH);
1065 }
1066 TEST(wait_twoproc_test, "two proc wait");
1067 
1068 static void
maxvalue_test(void)1069 maxvalue_test(void)
1070 {
1071 	semid_t id;
1072 	int val;
1073 
1074 	if (ksem_init(&id, SEM_VALUE_MAX) < 0) {
1075 		fail_errno("ksem_init");
1076 		return;
1077 	}
1078 	if (ksem_getvalue(id, &val) < 0) {
1079 		fail_errno("ksem_getvalue");
1080 		ksem_destroy(id);
1081 		return;
1082 	}
1083 	if (val != SEM_VALUE_MAX) {
1084 		fail_err("value %d != SEM_VALUE_MAX");
1085 		ksem_destroy(id);
1086 		return;
1087 	}
1088 	if (val < 0) {
1089 		fail_err("value < 0");
1090 		ksem_destroy(id);
1091 		return;
1092 	}
1093 	if (ksem_destroy(id) < 0) {
1094 		fail_errno("ksem_destroy");
1095 		return;
1096 	}
1097 	pass();
1098 }
1099 TEST(maxvalue_test, "get value of SEM_VALUE_MAX semaphore");
1100 
1101 static void
maxvalue_post_test(void)1102 maxvalue_post_test(void)
1103 {
1104 	semid_t id;
1105 
1106 	if (ksem_init(&id, SEM_VALUE_MAX) < 0) {
1107 		fail_errno("ksem_init");
1108 		return;
1109 	}
1110 
1111 	ksem_post_should_fail(id, EOVERFLOW);
1112 
1113 	ksem_destroy(id);
1114 }
1115 TEST(maxvalue_post_test, "post SEM_VALUE_MAX semaphore");
1116 
1117 static void
busy_destroy_test(void)1118 busy_destroy_test(void)
1119 {
1120 	char errbuf[_POSIX2_LINE_MAX];
1121 	struct kinfo_proc *kp;
1122 	semid_t id;
1123 	pid_t pid;
1124 	kvm_t *kd;
1125 	int count;
1126 
1127 	kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, errbuf);
1128 	if (kd == NULL) {
1129 		fail_err("kvm_openfiles: %s", errbuf);
1130 		return;
1131 	}
1132 
1133 	if (ksem_init(&id, 0) < 0) {
1134 		fail_errno("ksem_init");
1135 		kvm_close(kd);
1136 		return;
1137 	}
1138 
1139 	pid = fork();
1140 	switch (pid) {
1141 	case -1:
1142 		/* Error. */
1143 		fail_errno("fork");
1144 		ksem_destroy(id);
1145 		kvm_close(kd);
1146 		return;
1147 	case 0:
1148 		/* Child. */
1149 		ksem_wait(id);
1150 		exit(0);
1151 	}
1152 
1153 	/*
1154 	 * Wait for the child process to block on the semaphore.  This
1155 	 * is a bit gross.
1156 	 */
1157 	for (;;) {
1158 		kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &count);
1159 		if (kp == NULL) {
1160 			fail_err("kvm_getprocs: %s", kvm_geterr(kd));
1161 			kvm_close(kd);
1162 			ksem_destroy(id);
1163 			return;
1164 		}
1165 		if (kp->ki_stat == SSLEEP &&
1166 		    (strcmp(kp->ki_wmesg, "sem") == 0 ||
1167 		    strcmp(kp->ki_wmesg, "ksem") == 0))
1168 			break;
1169 		usleep(1000);
1170 	}
1171 	kvm_close(kd);
1172 
1173 	ksem_destroy_should_fail(id, EBUSY);
1174 
1175 	/* Cleanup. */
1176 	ksem_post(id);
1177 	waitpid(pid, NULL, 0);
1178 	ksem_destroy(id);
1179 }
1180 TEST(busy_destroy_test, "destroy unnamed semaphore with waiter");
1181 
1182 static int
exhaust_unnamed_child(void * arg)1183 exhaust_unnamed_child(void *arg)
1184 {
1185 	semid_t id;
1186 	int i, max;
1187 
1188 	max = (intptr_t)arg;
1189 	for (i = 0; i < max + 1; i++) {
1190 		if (ksem_init(&id, 1) < 0) {
1191 			if (errno == ENOSPC)
1192 				return (CSTAT(0, 0));
1193 			return (CSTAT(1, errno));
1194 		}
1195 	}
1196 	return (CSTAT(2, 0));
1197 }
1198 
1199 static void
exhaust_unnamed_sems(void)1200 exhaust_unnamed_sems(void)
1201 {
1202 	size_t len;
1203 	int nsems_max, stat;
1204 
1205 	len = sizeof(nsems_max);
1206 	if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) <
1207 	    0) {
1208 		fail_errno("sysctl(p1003_1b.sem_nsems_max)");
1209 		return;
1210 	}
1211 
1212 	if (child_worker(exhaust_unnamed_child, (void *)(uintptr_t)nsems_max,
1213 	    &stat))
1214 		return;
1215 	errno = CSTAT_ERROR(stat);
1216 	switch (CSTAT_CLASS(stat)) {
1217 	case 0:
1218 		pass();
1219 		break;
1220 	case 1:
1221 		fail_errno("ksem_init");
1222 		break;
1223 	case 2:
1224 		fail_err("Limit of %d semaphores not enforced", nsems_max);
1225 		break;
1226 	default:
1227 		fail_err("bad child state %#x", stat);
1228 		break;
1229 	}
1230 }
1231 TEST(exhaust_unnamed_sems, "exhaust unnamed semaphores (1)");
1232 
1233 static int
exhaust_named_child(void * arg)1234 exhaust_named_child(void *arg)
1235 {
1236 	char buffer[64];
1237 	semid_t id;
1238 	int i, max;
1239 
1240 	max = (intptr_t)arg;
1241 	for (i = 0; i < max + 1; i++) {
1242 		snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
1243 		if (ksem_open(&id, buffer, O_CREAT, 0777, 1) < 0) {
1244 			if (errno == ENOSPC || errno == EMFILE ||
1245 			    errno == ENFILE)
1246 				return (CSTAT(0, 0));
1247 			return (CSTAT(1, errno));
1248 		}
1249 	}
1250 	return (CSTAT(2, errno));
1251 }
1252 
1253 static void
exhaust_named_sems(void)1254 exhaust_named_sems(void)
1255 {
1256 	char buffer[64];
1257 	size_t len;
1258 	int i, nsems_max, stat;
1259 
1260 	len = sizeof(nsems_max);
1261 	if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) <
1262 	    0) {
1263 		fail_errno("sysctl(p1003_1b.sem_nsems_max)");
1264 		return;
1265 	}
1266 
1267 	if (child_worker(exhaust_named_child, (void *)(uintptr_t)nsems_max,
1268 	    &stat) < 0)
1269 		return;
1270 	errno = CSTAT_ERROR(stat);
1271 	switch (CSTAT_CLASS(stat)) {
1272 	case 0:
1273 		pass();
1274 		break;
1275 	case 1:
1276 		fail_errno("ksem_open");
1277 		break;
1278 	case 2:
1279 		fail_err("Limit of %d semaphores not enforced", nsems_max);
1280 		break;
1281 	default:
1282 		fail_err("bad child state %#x", stat);
1283 		break;
1284 	}
1285 
1286 	/* Cleanup any semaphores created by the child. */
1287 	for (i = 0; i < nsems_max + 1; i++) {
1288 		snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
1289 		ksem_unlink(buffer);
1290 	}
1291 }
1292 TEST(exhaust_named_sems, "exhaust named semaphores (1)");
1293 
1294 static int
fdlimit_set(void * arg)1295 fdlimit_set(void *arg)
1296 {
1297 	struct rlimit rlim;
1298 	int max;
1299 
1300 	max = (intptr_t)arg;
1301 	if (getrlimit(RLIMIT_NOFILE, &rlim) < 0)
1302 		return (CSTAT(3, errno));
1303 	rlim.rlim_cur = max;
1304 	if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
1305 		return (CSTAT(4, errno));
1306 	return (0);
1307 }
1308 
1309 static int
fdlimit_unnamed_child(void * arg)1310 fdlimit_unnamed_child(void *arg)
1311 {
1312 	int stat;
1313 
1314 	stat = fdlimit_set(arg);
1315 	if (stat == 0)
1316 		stat = exhaust_unnamed_child(arg);
1317 	return (stat);
1318 }
1319 
1320 static void
fdlimit_unnamed_sems(void)1321 fdlimit_unnamed_sems(void)
1322 {
1323 	int nsems_max, stat;
1324 
1325 	nsems_max = 10;
1326 	if (child_worker(fdlimit_unnamed_child, (void *)(uintptr_t)nsems_max,
1327 	    &stat))
1328 		return;
1329 	errno = CSTAT_ERROR(stat);
1330 	switch (CSTAT_CLASS(stat)) {
1331 	case 0:
1332 		pass();
1333 		break;
1334 	case 1:
1335 		fail_errno("ksem_init");
1336 		break;
1337 	case 2:
1338 		fail_err("Limit of %d semaphores not enforced", nsems_max);
1339 		break;
1340 	case 3:
1341 		fail_errno("getrlimit");
1342 		break;
1343 	case 4:
1344 		fail_errno("getrlimit");
1345 		break;
1346 	default:
1347 		fail_err("bad child state %#x", stat);
1348 		break;
1349 	}
1350 }
1351 TEST(fdlimit_unnamed_sems, "exhaust unnamed semaphores (2)");
1352 
1353 static int
fdlimit_named_child(void * arg)1354 fdlimit_named_child(void *arg)
1355 {
1356 	int stat;
1357 
1358 	stat = fdlimit_set(arg);
1359 	if (stat == 0)
1360 		stat = exhaust_named_child(arg);
1361 	return (stat);
1362 }
1363 
1364 static void
fdlimit_named_sems(void)1365 fdlimit_named_sems(void)
1366 {
1367 	char buffer[64];
1368 	int i, nsems_max, stat;
1369 
1370 	nsems_max = 10;
1371 	if (child_worker(fdlimit_named_child, (void *)(uintptr_t)nsems_max,
1372 	    &stat) < 0)
1373 		return;
1374 	errno = CSTAT_ERROR(stat);
1375 	switch (CSTAT_CLASS(stat)) {
1376 	case 0:
1377 		pass();
1378 		break;
1379 	case 1:
1380 		fail_errno("ksem_open");
1381 		break;
1382 	case 2:
1383 		fail_err("Limit of %d semaphores not enforced", nsems_max);
1384 		break;
1385 	case 3:
1386 		fail_errno("getrlimit");
1387 		break;
1388 	case 4:
1389 		fail_errno("getrlimit");
1390 		break;
1391 	default:
1392 		fail_err("bad child state %#x", stat);
1393 		break;
1394 	}
1395 
1396 	/* Cleanup any semaphores created by the child. */
1397 	for (i = 0; i < nsems_max + 1; i++) {
1398 		snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i);
1399 		ksem_unlink(buffer);
1400 	}
1401 }
1402 TEST(fdlimit_named_sems, "exhaust named semaphores (2)");
1403 
1404 int
main(int argc,char * argv[])1405 main(int argc, char *argv[])
1406 {
1407 
1408 	signal(SIGSYS, SIG_IGN);
1409 	run_tests();
1410 	return (0);
1411 }
1412