1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2024 Oxide Computer Company
14 */
15
16 /*
17 * semaphore-specific tests
18 */
19
20
21 #include <err.h>
22 #include <stdlib.h>
23 #include <semaphore.h>
24 #include <sys/debug.h>
25 #include <sys/sysmacros.h>
26 #include <stdbool.h>
27 #include <errno.h>
28 #include <string.h>
29
30 #include "clock_lock.h"
31
32 static void
clock_sem_create(const char * desc,void ** argp)33 clock_sem_create(const char *desc, void **argp)
34 {
35 int ret;
36 sem_t *sem;
37
38 sem = calloc(1, sizeof (sem_t));
39 if (sem == NULL) {
40 err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate memory "
41 "for a semaphore", desc);
42 }
43
44 if ((ret = sem_init(sem, 0, 1)) != 0) {
45 errc(EXIT_FAILURE, ret, "TEST FAILED: %s: failed to create "
46 "semaphore", desc);
47 }
48
49 *argp = sem;
50 }
51
52 static void
clock_sem_destroy(void * arg)53 clock_sem_destroy(void *arg)
54 {
55 VERIFY0(sem_destroy(arg));
56 free(arg);
57 }
58
59 static void
clock_sem_lock(void * arg)60 clock_sem_lock(void *arg)
61 {
62 VERIFY0(sem_trywait(arg));
63 }
64
65 static void
clock_sem_unlock(void * arg)66 clock_sem_unlock(void *arg)
67 {
68 VERIFY0(sem_post(arg));
69 }
70
71 const lock_ops_t clock_lock_sem_ops = {
72 .lo_create = clock_sem_create,
73 .lo_destroy = clock_sem_destroy,
74 .lo_lock = clock_sem_lock,
75 .lo_unlock = clock_sem_unlock
76 };
77
78 static bool
clock_test_sem_invalid_source(const clock_test_t * test,void * prim)79 clock_test_sem_invalid_source(const clock_test_t *test, void *prim)
80 {
81 bool ret = true;
82 sem_t *sem = prim;
83 const clockid_t clocks[] = { 0x7777, INT32_MAX, 0x23, CLOCK_VIRTUAL,
84 CLOCK_THREAD_CPUTIME_ID, CLOCK_PROCESS_CPUTIME_ID };
85
86 for (size_t i = 0; i < ARRAY_SIZE(clocks); i++) {
87 clockid_t c = clocks[i];
88
89 if (sem_clockwait(sem, c, &clock_to_100ms) != -1 ||
90 errno != EINVAL) {
91 warnx("TEST FAILED: %s: sem_clockwait with clock 0x%x "
92 "returned %s, not EINVAL", test->ct_desc, c,
93 strerrorname_np(errno));
94 ret = false;
95 }
96
97 if (sem_relclockwait_np(sem, c, &clock_to_100ms) !=
98 -1 || errno != EINVAL) {
99 warnx("TEST FAILED: %s: sem_relclockwait_np with clock "
100 "0x%x returned %s, not EINVAL", test->ct_desc, c,
101 strerrorname_np(errno));
102 ret = false;
103 }
104 }
105
106 return (ret);
107 }
108
109 static bool
clock_test_sem_inv_to_ign_abs(const clock_test_t * test,void * prim)110 clock_test_sem_inv_to_ign_abs(const clock_test_t *test, void *prim)
111 {
112 bool ret = true;
113 sem_t *sem = prim;
114
115 if (sem_timedwait(sem, &clock_to_invns) != 0) {
116 warnx("TEST FAILED: %s: sem_timedwait failed with an invalid "
117 "timeout when the lock when lock was available: expected "
118 "success, found %s", test->ct_desc, strerrorname_np(errno));
119 ret = false;
120 } else {
121 test->ct_ops->lo_unlock(sem);
122 }
123
124 if (sem_clockwait(sem, CLOCK_MONOTONIC, &clock_to_invns) != 0) {
125 warnx("TEST FAILED: %s: sem_clockwait failed with an invalid "
126 "timeout when the lock when lock was available: expected "
127 "success, found %s", test->ct_desc, strerrorname_np(errno));
128 ret = false;
129 } else {
130 test->ct_ops->lo_unlock(sem);
131 }
132
133 return (ret);
134 }
135
136 static bool
clock_test_sem_inv_to_abs(const clock_test_t * test,void * prim)137 clock_test_sem_inv_to_abs(const clock_test_t *test, void *prim)
138 {
139 bool ret = true;
140 sem_t *sem = prim;
141
142 if (sem_timedwait(sem, &clock_to_invns) != -1 || errno != EINVAL) {
143 warnx("TEST FAILED: %s: sem_timedwait with invalid timeout "
144 "returned %s, not EINVAL", test->ct_desc,
145 strerrorname_np(errno));
146 ret = false;
147 }
148
149 if (sem_clockwait(sem, CLOCK_MONOTONIC, &clock_to_invns) != -1 ||
150 errno != EINVAL) {
151 warnx("TEST FAILED: %s: sem_clockwait with invalid timeout "
152 "returned %s, not EINVAL", test->ct_desc,
153 strerrorname_np(errno));
154 ret = false;
155 }
156
157 return (ret);
158 }
159
160 static bool
clock_test_sem_inv_to_ign_rel(const clock_test_t * test,void * prim)161 clock_test_sem_inv_to_ign_rel(const clock_test_t *test, void *prim)
162 {
163 bool ret = true;
164 sem_t *sem = prim;
165 const struct timespec *specs[] = { &clock_to_invns, &clock_to_invnegs,
166 &clock_to_invnegns };
167 const char *descs[] = { "too many nanoseconds", "negative seconds",
168 "negative nanoseconds" };
169
170 for (size_t i = 0; i < ARRAY_SIZE(specs); i++) {
171 if (sem_reltimedwait_np(sem, specs[i]) != 0) {
172 warnx("TEST FAILED: %s: sem_reltimedwait_np "
173 "failed with invalid timeout %s when the lock when "
174 "lock was available: expected success, found %s",
175 test->ct_desc, descs[i], strerrorname_np(errno));
176 ret = false;
177 } else {
178 test->ct_ops->lo_unlock(sem);
179 }
180
181 if (sem_relclockwait_np(sem, CLOCK_MONOTONIC, specs[i]) != 0) {
182 warnx("TEST FAILED: %s: sem_relclockwait_np "
183 "failed with invalid timeout %s when the lock when "
184 "lock was available: expected success, found %s",
185 test->ct_desc, descs[i], strerrorname_np(errno));
186 ret = false;
187 } else {
188 test->ct_ops->lo_unlock(sem);
189 }
190 }
191
192 return (ret);
193 }
194
195 static bool
clock_test_sem_inv_to_rel(const clock_test_t * test,void * prim)196 clock_test_sem_inv_to_rel(const clock_test_t *test, void *prim)
197 {
198 bool ret = true;
199 sem_t *sem = prim;
200 const struct timespec *specs[] = { &clock_to_invns, &clock_to_invnegs,
201 &clock_to_invnegns };
202 const char *descs[] = { "too many nanoseconds", "negative seconds",
203 "negative nanoseconds" };
204
205 for (size_t i = 0; i < ARRAY_SIZE(specs); i++) {
206 if (sem_reltimedwait_np(sem, specs[i]) != -1 ||
207 errno != EINVAL) {
208 warnx("TEST FAILED: %s: sem_reltimedwait_np "
209 "with invalid timeout %s returned %s, not EINVAL",
210 test->ct_desc, descs[i], strerrorname_np(errno));
211 ret = false;
212 }
213
214 if (sem_relclockwait_np(sem, CLOCK_MONOTONIC, specs[i]) != -1 ||
215 errno != EINVAL) {
216 warnx("TEST FAILED: %s: sem_relclockwait_np "
217 "with invalid timeout %s returned %s, not EINVAL",
218 test->ct_desc, descs[i], strerrorname_np(errno));
219 ret = false;
220 }
221 }
222
223 return (ret);
224 }
225
226 static bool
clock_test_sem_to_abs(const clock_test_t * test,void * prim)227 clock_test_sem_to_abs(const clock_test_t *test, void *prim)
228 {
229 sem_t *sem = prim;
230 struct timespec to;
231 int p;
232 bool ret = true, elapse;
233
234 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to);
235 p = sem_timedwait(sem, &to);
236 elapse = clock_abs_after(CLOCK_REALTIME, &to);
237 if (p != -1 && errno != ETIMEDOUT) {
238 warnx("TEST FAILED: %s sem_timedwait on locked semaphore "
239 "returned %s, not ETIMEDOUT", test->ct_desc,
240 strerrorname_np(errno));
241 ret = false;
242 }
243 if (!elapse) {
244 warnx("TEST FAILED: %s: sem_timedwait on locked semaphore "
245 "did not block long enough!", test->ct_desc);
246 ret = false;
247 }
248
249 clock_rel_to_abs(CLOCK_REALTIME, &clock_to_100ms, &to);
250 p = sem_clockwait(sem, CLOCK_REALTIME, &to);
251 elapse = clock_abs_after(CLOCK_REALTIME, &to);
252 if (p != -1 && errno != ETIMEDOUT) {
253 warnx("TEST FAILED: %s: sem_clockwait on locked semaphore "
254 "with CLOCK_REALTIME returned %s, not ETIMEDOUT",
255 test->ct_desc, strerrorname_np(errno));
256 ret = false;
257 }
258 if (!elapse) {
259 warnx("TEST FAILED: %s: sem_clockwait on locked semaphore "
260 "with CLOCK_REALTIME did not block long enough!",
261 test->ct_desc);
262 ret = false;
263 }
264
265 clock_rel_to_abs(CLOCK_HIGHRES, &clock_to_100ms, &to);
266 p = sem_clockwait(sem, CLOCK_HIGHRES, &to);
267 elapse = clock_abs_after(CLOCK_HIGHRES, &to);
268 if (p != -1 && errno != ETIMEDOUT) {
269 warnx("TEST FAILED: %s: sem_clockwait on locked semaphore "
270 "with CLOCK_HIGHRES returned %s, not ETIMEDOUT",
271 test->ct_desc, strerrorname_np(errno));
272 ret = false;
273 }
274 if (!elapse) {
275 warnx("TEST FAILED: %s: sem_clockwait on locked semaphore "
276 "with CLOCK_HIGHRES did not block long enough!",
277 test->ct_desc);
278 ret = false;
279 }
280
281 return (ret);
282 }
283
284 static bool
clock_test_sem_to_rel(const clock_test_t * test,void * prim)285 clock_test_sem_to_rel(const clock_test_t *test, void *prim)
286 {
287 sem_t *sem = prim;
288 struct timespec start;
289 int p;
290 bool ret = true, elapse;
291
292 if (clock_gettime(CLOCK_REALTIME, &start) != 0) {
293 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME);
294 }
295 p = sem_reltimedwait_np(sem, &clock_to_100ms);
296 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms);
297 if (p != -1 && errno != ETIMEDOUT) {
298 warnx("TEST FAILED: %s: sem_reltimedwait_np on locked "
299 "sempahore returned %s, not ETIMEDOUT", test->ct_desc,
300 strerrorname_np(errno));
301 ret = false;
302 }
303 if (!elapse) {
304 warnx("TEST FAILED: %s: sem_reltimedwait_np on locked "
305 "sempahore did not block long enough!", test->ct_desc);
306 ret = false;
307 }
308
309 if (clock_gettime(CLOCK_REALTIME, &start) != 0) {
310 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_REALTIME);
311 }
312 p = sem_relclockwait_np(sem, CLOCK_REALTIME,
313 &clock_to_100ms);
314 elapse = clock_rel_after(CLOCK_REALTIME, &start, &clock_to_100ms);
315 if (p != -1 && errno != ETIMEDOUT) {
316 warnx("TEST FAILED: %s: sem_relclockwait_np on locked "
317 "semaphore with CLOCK_REALTIME returned %s, not ETIMEDOUT",
318 test->ct_desc, strerrorname_np(errno));
319 ret = false;
320 }
321 if (!elapse) {
322 warnx("TEST FAILED: %s: sem_relclockwait_np on locked "
323 "sempahore with CLOCK_REALTIME did not block long enough!",
324 test->ct_desc);
325 ret = false;
326 }
327
328 if (clock_gettime(CLOCK_HIGHRES, &start) != 0) {
329 err(EXIT_FAILURE, "failed to read clock %d", CLOCK_HIGHRES);
330 }
331 p = sem_relclockwait_np(sem, CLOCK_HIGHRES,
332 &clock_to_100ms);
333 elapse = clock_rel_after(CLOCK_HIGHRES, &start, &clock_to_100ms);
334 if (p != -1 && errno != ETIMEDOUT) {
335 warnx("TEST FAILED: %s: sem_relclockwait_np on locked "
336 "semaphore with CLOCK_HIGHRES returned %s, not ETIMEDOUT",
337 test->ct_desc, strerrorname_np(errno));
338 ret = false;
339 }
340 if (!elapse) {
341 warnx("TEST FAILED: %s: sem_relclockwait_np on locked "
342 "semaphore with CLOCK_HIGHRES did not block long enough!",
343 test->ct_desc);
344 ret = false;
345 }
346
347 return (ret);
348 }
349
350 const clock_test_t clock_sem_tests[] = { {
351 .ct_desc = "sem: invalid and unsupported clock sources",
352 .ct_ops = &clock_lock_sem_ops,
353 .ct_test = clock_test_sem_invalid_source
354 }, {
355 .ct_desc = "sem: invalid timeout works if lock available (absolute)",
356 .ct_ops = &clock_lock_sem_ops,
357 .ct_test = clock_test_sem_inv_to_ign_abs
358 }, {
359 .ct_desc = "sem: invalid timeout works if lock available (relative)",
360 .ct_ops = &clock_lock_sem_ops,
361 .ct_test = clock_test_sem_inv_to_ign_rel
362 }, {
363 .ct_desc = "sem: invalid timeout fails if lock taken (absolute)",
364 .ct_ops = &clock_lock_sem_ops,
365 .ct_test = clock_test_sem_inv_to_abs,
366 .ct_enter = true
367 }, {
368 .ct_desc = "sem: invalid timeout fails if lock taken (relative)",
369 .ct_ops = &clock_lock_sem_ops,
370 .ct_test = clock_test_sem_inv_to_rel,
371 .ct_enter = true
372 }, {
373 .ct_desc = "sem: timeout fires correctly (absolute)",
374 .ct_ops = &clock_lock_sem_ops,
375 .ct_test = clock_test_sem_to_abs,
376 .ct_enter = true
377 }, {
378 .ct_desc = "sem: timeout fires correctly (relative)",
379 .ct_ops = &clock_lock_sem_ops,
380 .ct_test = clock_test_sem_to_rel,
381 .ct_enter = true
382 } };
383
384 size_t clock_sem_ntests = ARRAY_SIZE(clock_sem_tests);
385