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