1 /*
2  * Copyright (c) 2006-2021, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2010-10-26     Bernard      the first version
9  */
10 
11 #include <rtthread.h>
12 #include <string.h>
13 #include <fcntl.h>
14 #include <sys/errno.h>
15 #include "semaphore.h"
16 
17 static sem_t *posix_sem_list = RT_NULL;
18 static struct rt_semaphore posix_sem_lock;
19 
20 /* initialize posix semaphore */
posix_sem_system_init(void)21 static int posix_sem_system_init(void)
22 {
23     rt_sem_init(&posix_sem_lock, "psem", 1, RT_IPC_FLAG_FIFO);
24     return 0;
25 }
26 INIT_COMPONENT_EXPORT(posix_sem_system_init);
27 
28 /**
29  * @brief   Inserts a semaphore into the linked list of semaphores.
30  * @param   psem Pointer to the semaphore structure to be inserted.
31  *
32  * @note    This function inserts the specified semaphore into a linked list of semaphores.
33  *          The newly inserted semaphore becomes the head of the list.
34  *          It updates the 'next' pointer of the semaphore structure to link it to the
35  *          current head of the list and then sets the head of the list to point to the
36  *          newly inserted semaphore.
37  */
posix_sem_insert(sem_t * psem)38 rt_inline void posix_sem_insert(sem_t *psem)
39 {
40     psem->next = posix_sem_list;
41     posix_sem_list = psem;
42 }
43 
44 /**
45  * @brief   Deletes a semaphore from the linked list of semaphores.
46  * @param   psem Pointer to the semaphore structure to be deleted.
47  *
48  * @note    This function deletes the specified semaphore from a linked list of semaphores.
49  *          If the semaphore to be deleted is the head of the list, it updates the head of the list
50  *          to point to the next semaphore. Otherwise, it traverses the list to find the semaphore
51  *          to be deleted and updates the 'next' pointer of the preceding semaphore to skip over
52  *          the semaphore to be deleted.
53  *          After deleting the semaphore, it also deletes the underlying RT-Thread semaphore if it exists
54  *          and frees the memory associated with the semaphore structure if it's not unnamed.
55  */
posix_sem_delete(sem_t * psem)56 static void posix_sem_delete(sem_t *psem)
57 {
58     sem_t *iter;
59     if (posix_sem_list == psem)
60     {
61         posix_sem_list = psem->next;
62 
63         rt_sem_delete(psem->sem);
64 
65         if(psem->unamed == 0)
66             rt_free(psem);
67 
68         return;
69     }
70     for (iter = posix_sem_list; iter->next != RT_NULL; iter = iter->next)
71     {
72         if (iter->next == psem)
73         {
74             /* delete this mq */
75             if (psem->next != RT_NULL)
76                 iter->next = psem->next;
77             else
78                 iter->next = RT_NULL;
79 
80             /* delete RT-Thread mqueue */
81             rt_sem_delete(psem->sem);
82 
83             if(psem->unamed == 0)
84                 rt_free(psem);
85 
86             return ;
87         }
88     }
89 }
90 
91 /**
92  * @brief   Finds a semaphore by name in the linked list of semaphores.
93  * @param   name Pointer to the name of the semaphore to be found.
94  * @return  Pointer to the semaphore structure if found; otherwise, RT_NULL.
95  *
96  * @note    This function searches for a semaphore with the specified name in the linked list of semaphores.
97  *          It iterates through the list and compares the name of each semaphore with the given name.
98  *          If a semaphore with a matching name is found, a pointer to its structure is returned.
99  *          Otherwise, RT_NULL is returned to indicate that no semaphore with the given name was found.
100  */
posix_sem_find(const char * name)101 static sem_t *posix_sem_find(const char* name)
102 {
103     sem_t *iter;
104     rt_object_t object;
105 
106     for (iter = posix_sem_list; iter != RT_NULL; iter = iter->next)
107     {
108         object = (rt_object_t)iter->sem;
109 
110         if (strncmp(object->name, name, RT_NAME_MAX) == 0)
111         {
112             return iter;
113         }
114     }
115 
116     return RT_NULL;
117 }
118 
119 /**
120  * @brief   Closes a POSIX semaphore.
121  * @param   sem Pointer to the semaphore to be closed.
122  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
123  *
124  * @note    This function decreases the reference count of the specified semaphore.
125  *          If the reference count reaches zero, the semaphore is removed from the list
126  *          of POSIX semaphores if it was unlinked. The memory associated with the semaphore
127  *          is not immediately freed; instead, it will be freed when the last reference to
128  *          the semaphore is released.
129  */
sem_close(sem_t * sem)130 int sem_close(sem_t *sem)
131 {
132     if (sem == RT_NULL)
133     {
134         rt_set_errno(EINVAL);
135 
136         return -1;
137     }
138 
139     /* lock posix semaphore list */
140     rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
141     sem->refcount --;
142     if (sem->refcount == 0)
143     {
144         /* delete from posix semaphore list */
145         if (sem->unlinked)
146             posix_sem_delete(sem);
147         sem = RT_NULL;
148     }
149     rt_sem_release(&posix_sem_lock);
150 
151     return 0;
152 }
153 RTM_EXPORT(sem_close);
154 
155 /**
156  * @brief   Destroys a POSIX semaphore.
157  * @param   sem Pointer to the semaphore to be destroyed.
158  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
159  *
160  * @note    This function destroys an unnamed POSIX semaphore.
161  *          It first checks if the semaphore pointer is valid and if the semaphore is unnamed.
162  *          If the semaphore is still in use (i.e., there are threads waiting on it),
163  *          the function returns with an error code (EBUSY) without destroying the semaphore.
164  *          Otherwise, it removes the semaphore from the list of POSIX semaphores and frees
165  *          the memory associated with the semaphore structure.
166  */
sem_destroy(sem_t * sem)167 int sem_destroy(sem_t *sem)
168 {
169     if ((!sem) || !(sem->unamed))
170     {
171         rt_set_errno(EINVAL);
172 
173         return -1;
174     }
175 
176     /* lock posix semaphore list */
177     rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
178     if(rt_list_len(&sem->sem->parent.suspend_thread) != 0)
179     {
180         rt_sem_release(&posix_sem_lock);
181         rt_set_errno(EBUSY);
182 
183         return -1;
184     }
185 
186     /* destroy an unamed posix semaphore */
187     posix_sem_delete(sem);
188     rt_sem_release(&posix_sem_lock);
189 
190     return 0;
191 }
192 RTM_EXPORT(sem_destroy);
193 
194 /**
195  * @brief   Unlinks a named POSIX semaphore.
196  * @param   name Pointer to the name of the semaphore to be unlinked.
197  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
198  *
199  * @note    This function unlinks a named POSIX semaphore identified by the given name.
200  *          It first searches for the semaphore with the specified name in the list of
201  *          POSIX semaphores. If the semaphore is found, it marks the semaphore as unlinked.
202  *          If the reference count of the semaphore is zero, indicating that no threads are
203  *          currently using the semaphore, it removes the semaphore from the list and frees
204  *          the associated memory. Otherwise, the semaphore is not immediately removed; it
205  *          will be removed when its reference count reaches zero.
206  */
sem_unlink(const char * name)207 int sem_unlink(const char *name)
208 {
209     sem_t *psem;
210 
211     /* lock posix semaphore list */
212     rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
213     psem = posix_sem_find(name);
214     if (psem != RT_NULL)
215     {
216         psem->unlinked = 1;
217         if (psem->refcount == 0)
218         {
219             /* remove this semaphore */
220             posix_sem_delete(psem);
221         }
222         rt_sem_release(&posix_sem_lock);
223 
224         return 0;
225     }
226     rt_sem_release(&posix_sem_lock);
227 
228     /* no this entry */
229     rt_set_errno(ENOENT);
230 
231     return -1;
232 }
233 RTM_EXPORT(sem_unlink);
234 
235 /**
236  * @brief   Retrieves the value of a POSIX semaphore.
237  * @param   sem Pointer to the semaphore.
238  * @param   sval Pointer to an integer where the semaphore value will be stored.
239  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
240  *
241  * @note    This function retrieves the current value of the specified POSIX semaphore.
242  *          It copies the semaphore value into the memory location pointed to by sval.
243  *          If either sem or sval is a null pointer, the function sets errno to EINVAL
244  *          to indicate an invalid argument and returns -1.
245  */
sem_getvalue(sem_t * sem,int * sval)246 int sem_getvalue(sem_t *sem, int *sval)
247 {
248     if (!sem || !sval)
249     {
250         rt_set_errno(EINVAL);
251 
252         return -1;
253     }
254     *sval = sem->sem->value;
255 
256     return 0;
257 }
258 RTM_EXPORT(sem_getvalue);
259 
260 /**
261  * @brief   Initializes a POSIX semaphore.
262  * @param   sem Pointer to the semaphore structure to be initialized.
263  * @param   pshared Flag indicating whether the semaphore is to be shared between processes.
264  * @param   value Initial value of the semaphore.
265  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
266  *
267  * @note    This function initializes a POSIX semaphore with the specified initial value.
268  *          If sem is a null pointer, the function sets errno to EINVAL to indicate an invalid argument and returns -1.
269  *          The pshared parameter is not used in this implementation, as all semaphores are created as local (unshared).
270  *          The value parameter specifies the initial value of the semaphore.
271  *          The semaphore is given a unique name using a static counter, and it is created using the RT-Thread semaphore
272  *          creation function rt_sem_create(). If memory allocation fails during semaphore creation, errno is set to ENOMEM.
273  *          After successful initialization, the semaphore structure is inserted into the linked list of POSIX semaphores.
274  */
sem_init(sem_t * sem,int pshared,unsigned int value)275 int sem_init(sem_t *sem, int pshared, unsigned int value)
276 {
277     char name[RT_NAME_MAX];
278     static rt_uint16_t psem_number = 0;
279 
280     if (sem == RT_NULL)
281     {
282         rt_set_errno(EINVAL);
283 
284         return -1;
285     }
286 
287     rt_snprintf(name, sizeof(name), "psem%02d", psem_number++);
288     sem->sem = rt_sem_create(name, value, RT_IPC_FLAG_FIFO);
289     if (sem->sem == RT_NULL)
290     {
291         rt_set_errno(ENOMEM);
292 
293         return -1;
294     }
295 
296     /* initialize posix semaphore */
297     sem->refcount = 1;
298     sem->unlinked = 0;
299     sem->unamed = 1;
300     /* lock posix semaphore list */
301     rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
302     posix_sem_insert(sem);
303     rt_sem_release(&posix_sem_lock);
304 
305     return 0;
306 }
307 RTM_EXPORT(sem_init);
308 
309 /**
310  * @brief   Opens or creates a POSIX semaphore.
311  * @param   name Pointer to the name of the semaphore.
312  * @param   oflag Bitwise OR of flags indicating the operation mode.
313  * @param   ... Additional arguments (optional).
314  * @return  Upon successful completion, returns a pointer to the semaphore;
315  *          otherwise, returns RT_NULL and sets errno to indicate the error.
316  *
317  * @note    This function opens or creates a POSIX semaphore specified by the name argument.
318  *          If the oflag argument includes O_CREAT, the semaphore is created if it does not already exist.
319  *          Additional arguments may include the mode (not used in this implementation) and the initial value of the semaphore.
320  *          If the oflag argument includes O_EXCL along with O_CREAT, the function fails if the semaphore already exists.
321  *          If memory allocation fails during semaphore creation, errno is set to ENFILE.
322  *          After successful creation or opening, the semaphore's reference count is incremented, and it is inserted into
323  *          the linked list of POSIX semaphores. If the semaphore already exists and is successfully opened, its reference
324  *          count is incremented. If the semaphore cannot be opened or created for any reason, errno is set to indicate the error,
325  *          and the function returns RT_NULL.
326  */
sem_open(const char * name,int oflag,...)327 sem_t *sem_open(const char *name, int oflag, ...)
328 {
329     sem_t* sem;
330     va_list arg;
331     mode_t mode;
332     unsigned int value;
333 
334     sem = RT_NULL;
335 
336     /* lock posix semaphore list */
337     rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
338     if (oflag & O_CREAT)
339     {
340         va_start(arg, oflag);
341         mode = (mode_t) va_arg( arg, unsigned int); mode = mode;
342         value = va_arg( arg, unsigned int);
343         va_end(arg);
344 
345         if (oflag & O_EXCL)
346         {
347             if (posix_sem_find(name) != RT_NULL)
348             {
349                 rt_set_errno(EEXIST);
350                 goto __return;
351             }
352         }
353         sem = (sem_t*) rt_malloc (sizeof(struct posix_sem));
354         if (sem == RT_NULL)
355         {
356             rt_set_errno(ENFILE);
357             goto __return;
358         }
359 
360         /* create RT-Thread semaphore */
361         sem->sem = rt_sem_create(name, value, RT_IPC_FLAG_FIFO);
362         if (sem->sem == RT_NULL) /* create failed */
363         {
364             rt_set_errno(ENFILE);
365             goto __return;
366         }
367         /* initialize reference count */
368         sem->refcount = 1;
369         sem->unlinked = 0;
370         sem->unamed = 0;
371 
372         /* insert semaphore to posix semaphore list */
373         posix_sem_insert(sem);
374     }
375     else
376     {
377         /* find semaphore */
378         sem = posix_sem_find(name);
379         if (sem != RT_NULL)
380         {
381             sem->refcount ++; /* increase reference count */
382         }
383         else
384         {
385             rt_set_errno(ENOENT);
386             goto __return;
387         }
388     }
389     rt_sem_release(&posix_sem_lock);
390 
391     return sem;
392 
393 __return:
394     /* release lock */
395     rt_sem_release(&posix_sem_lock);
396 
397     /* release allocated memory */
398     if (sem != RT_NULL)
399     {
400         /* delete RT-Thread semaphore */
401         if (sem->sem != RT_NULL)
402             rt_sem_delete(sem->sem);
403         rt_free(sem);
404     }
405 
406     return RT_NULL;
407 }
408 RTM_EXPORT(sem_open);
409 
410 /**
411  * @brief   Posts (increments) a POSIX semaphore.
412  * @param   sem Pointer to the semaphore.
413  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
414  *
415  * @note    This function increments the value of the specified POSIX semaphore by one.
416  *          If sem is a null pointer, errno is set to EINVAL to indicate an invalid argument, and the function returns -1.
417  *          The semaphore is released using the RT-Thread semaphore release function rt_sem_release().
418  *          If the semaphore release operation succeeds, the function returns 0; otherwise, errno is set to EINVAL,
419  *          indicating an error, and the function returns -1.
420  */
sem_post(sem_t * sem)421 int sem_post(sem_t *sem)
422 {
423     rt_err_t result;
424 
425     if (!sem)
426     {
427         rt_set_errno(EINVAL);
428 
429         return -1;
430     }
431 
432     result = rt_sem_release(sem->sem);
433     if (result == RT_EOK)
434         return 0;
435 
436     rt_set_errno(EINVAL);
437 
438     return -1;
439 }
440 RTM_EXPORT(sem_post);
441 
442 /**
443  * @brief   Waits for a POSIX semaphore with a timeout.
444  * @param   sem Pointer to the semaphore.
445  * @param   abs_timeout Pointer to the absolute timeout value.
446  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
447  *
448  * @note    This function waits for the specified POSIX semaphore to become available within the specified timeout.
449  *          If either sem or abs_timeout is a null pointer, the function returns EINVAL, indicating an invalid argument.
450  *          The abs_timeout parameter specifies an absolute timeout value based on the CLOCK_REALTIME clock.
451  *          The timeout is converted to RT-Thread ticks using the rt_timespec_to_tick() function.
452  *          The semaphore is waited upon using the RT-Thread semaphore take function rt_sem_take().
453  *          If the semaphore is successfully acquired within the specified timeout, the function returns 0.
454  *          If the timeout expires before the semaphore becomes available, errno is set to ETIMEDOUT,
455  *          and the function returns -1. If the semaphore wait operation is interrupted by a signal,
456  *          errno is set to EINTR, and the function returns -1.
457  */
sem_timedwait(sem_t * sem,const struct timespec * abs_timeout)458 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)
459 {
460     rt_err_t result;
461     rt_int32_t tick;
462 
463     if (!sem || !abs_timeout)
464         return EINVAL;
465 
466     /* calculate os tick */
467     tick = rt_timespec_to_tick(abs_timeout);
468 
469     result = rt_sem_take(sem->sem, tick);
470     if (result == -RT_ETIMEOUT)
471     {
472         rt_set_errno(ETIMEDOUT);
473 
474         return -1;
475     }
476     if (result == RT_EOK)
477         return 0;
478 
479     rt_set_errno(EINTR);
480 
481     return -1;
482 }
483 RTM_EXPORT(sem_timedwait);
484 
485 /**
486  * @brief   Attempts to wait for a POSIX semaphore without blocking.
487  * @param   sem Pointer to the semaphore.
488  * @return  Upon successful completion, returns 0 if the semaphore was acquired;
489  *          otherwise, returns -1 and sets errno to indicate the error.
490  *
491  * @note    This function attempts to acquire the specified POSIX semaphore without blocking.
492  *          If sem is a null pointer, errno is set to EINVAL to indicate an invalid argument, and the function returns -1.
493  *          The semaphore is waited upon using the RT-Thread semaphore take function rt_sem_take() with a timeout of 0,
494  *          meaning that the function does not block if the semaphore is not available.
495  *          If the semaphore is successfully acquired, the function returns 0. If the semaphore is not available,
496  *          errno is set to EAGAIN to indicate that the operation would result in blocking, and the function returns -1.
497  *          If the semaphore wait operation is interrupted by a signal, errno is set to EINTR, and the function returns -1.
498  */
sem_trywait(sem_t * sem)499 int sem_trywait(sem_t *sem)
500 {
501     rt_err_t result;
502 
503     if (!sem)
504     {
505         rt_set_errno(EINVAL);
506 
507         return -1;
508     }
509 
510     result = rt_sem_take(sem->sem, 0);
511     if (result == -RT_ETIMEOUT)
512     {
513         rt_set_errno(EAGAIN);
514 
515         return -1;
516     }
517     if (result == RT_EOK)
518         return 0;
519 
520     rt_set_errno(EINTR);
521 
522     return -1;
523 }
524 RTM_EXPORT(sem_trywait);
525 
526 /**
527  * @brief   Waits indefinitely for a POSIX semaphore to become available.
528  * @param   sem Pointer to the semaphore.
529  * @return  Upon successful completion, returns 0; otherwise, returns -1 and sets errno to indicate the error.
530  *
531  * @note    This function waits indefinitely for the specified POSIX semaphore to become available.
532  *          If sem is a null pointer, errno is set to EINVAL to indicate an invalid argument, and the function returns -1.
533  *          The semaphore is waited upon using the RT-Thread semaphore take function rt_sem_take() with a timeout
534  *          value of RT_WAITING_FOREVER, indicating an infinite wait time.
535  *          If the semaphore is successfully acquired, the function returns 0. If the semaphore wait operation is interrupted
536  *          by a signal, errno is set to EINTR, and the function returns -1.
537  */
sem_wait(sem_t * sem)538 int sem_wait(sem_t *sem)
539 {
540     rt_err_t result;
541 
542     if (!sem)
543     {
544         rt_set_errno(EINVAL);
545 
546         return -1;
547     }
548 
549     result = rt_sem_take(sem->sem, RT_WAITING_FOREVER);
550     if (result == RT_EOK)
551         return 0;
552 
553     rt_set_errno(EINTR);
554 
555     return -1;
556 }
557 RTM_EXPORT(sem_wait);
558 
559