1 /*
2 * Copyright (c) 2018 Intel Corporation
3 * Copyright (c) 2023 Meta
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12
13 #include <zephyr/sys/util.h>
14 #include <zephyr/ztest.h>
15
child_func(void * p1)16 static void *child_func(void *p1)
17 {
18 sem_t *sem = (sem_t *)p1;
19
20 zassert_equal(sem_post(sem), 0, "sem_post failed");
21 return NULL;
22 }
23
semaphore_test(sem_t * sem)24 static void semaphore_test(sem_t *sem)
25 {
26 pthread_t thread1, thread2;
27 int val, ret;
28 struct timespec abstime;
29
30 /* TESTPOINT: Check if sema value is less than
31 * CONFIG_POSIX_SEM_VALUE_MAX
32 */
33 zassert_equal(sem_init(sem, 0, (CONFIG_POSIX_SEM_VALUE_MAX + 1)), -1,
34 "value larger than %d\n", CONFIG_POSIX_SEM_VALUE_MAX);
35 zassert_equal(errno, EINVAL);
36
37 zassert_equal(sem_init(sem, 0, 0), 0, "sem_init failed");
38
39 /* TESTPOINT: Check if semaphore value is as set */
40 zassert_equal(sem_getvalue(sem, &val), 0);
41 zassert_equal(val, 0);
42
43 /* TESTPOINT: Check if sema is acquired when it
44 * is not available
45 */
46 zassert_equal(sem_trywait(sem), -1);
47 zassert_equal(errno, EAGAIN);
48
49 ret = pthread_create(&thread1, NULL, child_func, sem);
50 zassert_equal(ret, 0, "Thread creation failed");
51
52 zassert_equal(clock_gettime(CLOCK_REALTIME, &abstime), 0, "clock_gettime failed");
53
54 abstime.tv_sec += 5;
55
56 /* TESPOINT: Wait for 5 seconds and acquire sema given
57 * by thread1
58 */
59 zassert_equal(sem_timedwait(sem, &abstime), 0);
60
61 /* TESTPOINT: Semaphore is already acquired, check if
62 * no semaphore is available
63 */
64 zassert_equal(sem_timedwait(sem, &abstime), -1);
65 zassert_equal(errno, ETIMEDOUT);
66
67 zassert_equal(sem_destroy(sem), 0, "semaphore is not destroyed");
68
69 /* TESTPOINT: Initialize sema with 1 */
70 zassert_equal(sem_init(sem, 0, 1), 0, "sem_init failed");
71 zassert_equal(sem_getvalue(sem, &val), 0);
72 zassert_equal(val, 1);
73
74 zassert_equal(sem_destroy(sem), -1,
75 "acquired semaphore"
76 " is destroyed");
77 zassert_equal(errno, EBUSY);
78
79 /* TESTPOINT: take semaphore which is initialized with 1 */
80 zassert_equal(sem_trywait(sem), 0);
81
82 zassert_equal(pthread_create(&thread2, NULL, child_func, sem), 0, "Thread creation failed");
83
84 /* TESTPOINT: Wait and acquire semaphore till thread2 gives */
85 zassert_equal(sem_wait(sem), 0, "sem_wait failed");
86
87 /* Make sure the threads are terminated */
88 zassert_ok(pthread_join(thread1, NULL));
89 zassert_ok(pthread_join(thread2, NULL));
90 }
91
ZTEST(posix_semaphores,test_semaphore)92 ZTEST(posix_semaphores, test_semaphore)
93 {
94 sem_t sema;
95
96 /* TESTPOINT: Call sem_post with invalid kobject */
97 zassert_equal(sem_post(NULL), -1,
98 "sem_post of"
99 " invalid semaphore object didn't fail");
100 zassert_equal(errno, EINVAL);
101
102 /* TESTPOINT: sem_destroy with invalid kobject */
103 zassert_equal(sem_destroy(NULL), -1,
104 "invalid"
105 " semaphore is destroyed");
106 zassert_equal(errno, EINVAL);
107
108 semaphore_test(&sema);
109 }
110
111 int nsem_get_ref_count(sem_t *sem);
112 size_t nsem_get_list_len(void);
113
nsem_open_func(void * p)114 static void *nsem_open_func(void *p)
115 {
116 const char *name = (char *)p;
117
118 for (int i = 0; i < CONFIG_TEST_SEM_N_LOOPS; i++) {
119 zassert_not_null(sem_open(name, 0, 0, 0), "%s is NULL", name);
120 k_msleep(1);
121 }
122
123 /* Unlink after finished opening */
124 zassert_ok(sem_unlink(name));
125 return NULL;
126 }
127
nsem_close_func(void * p)128 static void *nsem_close_func(void *p)
129 {
130 sem_t *sem = (sem_t *)p;
131
132 /* Make sure that we have enough ref_count's initially */
133 k_msleep(CONFIG_TEST_SEM_N_LOOPS >> 1);
134
135 for (int i = 0; i < CONFIG_TEST_SEM_N_LOOPS; i++) {
136 zassert_ok(sem_close(sem));
137 k_msleep(1);
138 }
139
140 /* Close the last `sem` */
141 zassert_ok(sem_close(sem));
142 return NULL;
143 }
144
ZTEST(posix_semaphores,test_named_semaphore)145 ZTEST(posix_semaphores, test_named_semaphore)
146 {
147 pthread_t thread1, thread2;
148 sem_t *sem1, *sem2, *different_sem1;
149
150 /* If `name` is invalid */
151 sem1 = sem_open(NULL, 0, 0, 0);
152 zassert_equal(errno, EINVAL);
153 zassert_equal_ptr(sem1, SEM_FAILED);
154 zassert_equal(nsem_get_list_len(), 0);
155
156 /* Attempt to open a named sem that doesn't exist */
157 sem1 = sem_open("sem1", 0, 0, 0);
158 zassert_equal(errno, ENOENT);
159 zassert_equal_ptr(sem1, SEM_FAILED);
160 zassert_equal(nsem_get_list_len(), 0);
161
162 /* Name exceeds CONFIG_POSIX_SEM_NAMELEN_MAX */
163 char name_too_long[CONFIG_POSIX_SEM_NAMELEN_MAX + 2];
164
165 for (size_t i = 0; i < sizeof(name_too_long) - 1; i++) {
166 name_too_long[i] = 'a';
167 }
168 name_too_long[sizeof(name_too_long) - 1] = '\0';
169
170 sem1 = sem_open(name_too_long, 0, 0, 0);
171 zassert_equal(errno, ENAMETOOLONG, "\"%s\" should be longer than %d", name_too_long,
172 CONFIG_POSIX_SEM_NAMELEN_MAX);
173 zassert_equal_ptr(sem1, SEM_FAILED);
174 zassert_equal(nsem_get_list_len(), 0);
175
176 /* `value` greater than CONFIG_POSIX_SEM_VALUE_MAX */
177 sem1 = sem_open("sem1", O_CREAT, 0, (CONFIG_POSIX_SEM_VALUE_MAX + 1));
178 zassert_equal(errno, EINVAL);
179 zassert_equal_ptr(sem1, SEM_FAILED);
180 zassert_equal(nsem_get_list_len(), 0);
181
182 /* Open named sem */
183 sem1 = sem_open("sem1", O_CREAT, 0, 0);
184 zassert_equal(nsem_get_ref_count(sem1), 2);
185 zassert_equal(nsem_get_list_len(), 1);
186 sem2 = sem_open("sem2", O_CREAT, 0, 0);
187 zassert_equal(nsem_get_ref_count(sem2), 2);
188 zassert_equal(nsem_get_list_len(), 2);
189
190 /* Open created named sem repeatedly */
191 for (size_t i = 1; i <= CONFIG_TEST_SEM_N_LOOPS; i++) {
192 sem_t *new_sem1, *new_sem2;
193
194 /* oflags are ignored (except when both O_CREAT & O_EXCL are set) */
195 new_sem1 = sem_open("sem1", i % 2 == 0 ? O_CREAT : 0, 0, 0);
196 zassert_not_null(new_sem1);
197 zassert_equal_ptr(new_sem1, sem1); /* Should point to the same sem */
198 new_sem2 = sem_open("sem2", i % 2 == 0 ? O_CREAT : 0, 0, 0);
199 zassert_not_null(new_sem2);
200 zassert_equal_ptr(new_sem2, sem2);
201
202 /* ref_count should increment */
203 zassert_equal(nsem_get_ref_count(sem1), 2 + i);
204 zassert_equal(nsem_get_ref_count(sem2), 2 + i);
205
206 /* Should reuse the same named sem instead of creating another one */
207 zassert_equal(nsem_get_list_len(), 2);
208 }
209
210 /* O_CREAT and O_EXCL are set and the named semaphore already exists */
211 zassert_equal_ptr((sem_open("sem1", O_CREAT | O_EXCL, 0, 0)), SEM_FAILED);
212 zassert_equal(errno, EEXIST);
213 zassert_equal(nsem_get_list_len(), 2);
214
215 zassert_equal(sem_close(NULL), -1);
216 zassert_equal(errno, EINVAL);
217 zassert_equal(nsem_get_list_len(), 2);
218
219 /* Close sem */
220 for (size_t i = CONFIG_TEST_SEM_N_LOOPS;
221 /* close until one left, required by the test later */
222 i >= 1; i--) {
223 zassert_ok(sem_close(sem1));
224 zassert_equal(nsem_get_ref_count(sem1), 2 + i - 1);
225
226 zassert_ok(sem_close(sem2));
227 zassert_equal(nsem_get_ref_count(sem2), 2 + i - 1);
228
229 zassert_equal(nsem_get_list_len(), 2);
230 }
231
232 /* If `name` is invalid */
233 zassert_equal(sem_unlink(NULL), -1);
234 zassert_equal(errno, EINVAL);
235 zassert_equal(nsem_get_list_len(), 2);
236
237 /* Attempt to unlink a named sem that doesn't exist */
238 zassert_equal(sem_unlink("sem3"), -1);
239 zassert_equal(errno, ENOENT);
240 zassert_equal(nsem_get_list_len(), 2);
241
242 /* Name exceeds CONFIG_POSIX_SEM_NAMELEN_MAX */
243 char long_sem_name[CONFIG_POSIX_SEM_NAMELEN_MAX + 2];
244
245 for (int i = 0; i < CONFIG_POSIX_SEM_NAMELEN_MAX + 1; i++) {
246 long_sem_name[i] = 'a';
247 }
248 long_sem_name[CONFIG_POSIX_SEM_NAMELEN_MAX + 1] = '\0';
249
250 zassert_equal(sem_unlink(long_sem_name), -1);
251 zassert_equal(errno, ENAMETOOLONG);
252 zassert_equal(nsem_get_list_len(), 2);
253
254 /* Unlink sem1 when it is still being used */
255 zassert_equal(nsem_get_ref_count(sem1), 2);
256 zassert_ok(sem_unlink("sem1"));
257 /* sem won't be destroyed */
258 zassert_equal(nsem_get_ref_count(sem1), 1);
259 zassert_equal(nsem_get_list_len(), 2);
260
261 /* Create another sem with the name of an unlinked sem */
262 different_sem1 = sem_open("sem1", O_CREAT, 0, 0);
263 zassert_not_null(different_sem1);
264 /* The created sem will be a different instance */
265 zassert(different_sem1 != sem1, "");
266 zassert_equal(nsem_get_list_len(), 3);
267
268 /* Destruction of sem1 will be postponed until all references to the semaphore have been
269 * destroyed by calls to sem_close()
270 */
271 zassert_ok(sem_close(sem1));
272 zassert_equal(nsem_get_list_len(), 2);
273
274 /* Closing a linked sem won't destroy the sem */
275 zassert_ok(sem_close(sem2));
276 zassert_equal(nsem_get_ref_count(sem2), 1);
277 zassert_equal(nsem_get_list_len(), 2);
278
279 /* Instead the sem will be destroyed upon call to sem_unlink() */
280 zassert_ok(sem_unlink("sem2"));
281 zassert_equal(nsem_get_list_len(), 1);
282
283 /* What we have left open here is `different_sem` as "sem1", which has a ref_count of 2 */
284 zassert_equal(nsem_get_ref_count(different_sem1), 2);
285
286 /* Stress test: open & close "sem1" repeatedly */
287 zassert_ok(pthread_create(&thread1, NULL, nsem_open_func, "sem1"));
288 zassert_ok(pthread_create(&thread2, NULL, nsem_close_func, different_sem1));
289
290 /* Make sure the threads are terminated */
291 zassert_ok(pthread_join(thread1, NULL));
292 zassert_ok(pthread_join(thread2, NULL));
293
294 /* All named semaphores should be destroyed here */
295 zassert_equal(nsem_get_list_len(), 0);
296
297 /* Create a new named sem to be used in the normal semaphore test */
298 sem1 = sem_open("nsem", O_CREAT, 0, 0);
299 zassert_equal(nsem_get_list_len(), 1);
300 zassert_equal(nsem_get_ref_count(sem1), 2);
301
302 /* Run the semaphore test with the created named semaphore */
303 semaphore_test(sem1);
304
305 /* List length and ref_count shouldn't change after the test */
306 zassert_equal(nsem_get_list_len(), 1);
307 zassert_equal(nsem_get_ref_count(sem1), 2);
308
309 /* Unless it is unlinked and closed */
310 sem_unlink("nsem");
311 sem_close(sem1);
312 zassert_equal(nsem_get_list_len(), 0);
313 }
314
before(void * arg)315 static void before(void *arg)
316 {
317 ARG_UNUSED(arg);
318
319 if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) {
320 /* skip redundant testing if there is no thread pool / heap allocation */
321 ztest_test_skip();
322 }
323 }
324
325 ZTEST_SUITE(posix_semaphores, NULL, NULL, before, NULL, NULL);
326