xref: /haiku/src/system/kernel/posix/realtime_sem.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
1 /*
2  * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <posix/realtime_sem.h>
7 
8 #include <string.h>
9 
10 #include <new>
11 
12 #include <OS.h>
13 
14 #include <AutoDeleter.h>
15 #include <fs/KPath.h>
16 #include <kernel.h>
17 #include <lock.h>
18 #include <syscall_restart.h>
19 #include <team.h>
20 #include <thread.h>
21 #include <util/atomic.h>
22 #include <util/AutoLock.h>
23 #include <util/OpenHashTable.h>
24 #include <util/StringHash.h>
25 
26 
27 namespace {
28 
29 class SemInfo {
30 public:
31 	SemInfo()
32 		:
33 		fSemaphoreID(-1)
34 	{
35 	}
36 
37 	virtual ~SemInfo()
38 	{
39 		if (fSemaphoreID >= 0)
40 			delete_sem(fSemaphoreID);
41 	}
42 
43 	sem_id SemaphoreID() const			{ return fSemaphoreID; }
44 
45 	status_t Init(int32 semCount, const char* name)
46 	{
47 		fSemaphoreID = create_sem(semCount, name);
48 		if (fSemaphoreID < 0)
49 			return fSemaphoreID;
50 
51 		return B_OK;
52 	}
53 
54 	virtual sem_id ID() const = 0;
55 	virtual SemInfo* Clone() = 0;
56 	virtual void Delete() = 0;
57 
58 private:
59 	sem_id	fSemaphoreID;
60 };
61 
62 
63 class NamedSem : public SemInfo {
64 public:
65 	NamedSem()
66 		:
67 		fName(NULL),
68 		fRefCount(1)
69 	{
70 	}
71 
72 	virtual ~NamedSem()
73 	{
74 		free(fName);
75 	}
76 
77 	const char* Name() const		{ return fName; }
78 
79 	status_t Init(const char* name, mode_t mode, int32 semCount)
80 	{
81 		status_t error = SemInfo::Init(semCount, name);
82 		if (error != B_OK)
83 			return error;
84 
85 		fName = strdup(name);
86 		if (fName == NULL)
87 			return B_NO_MEMORY;
88 
89 		fUID = geteuid();
90 		fGID = getegid();
91 		fPermissions = mode;
92 
93 		return B_OK;
94 	}
95 
96 	void AcquireReference()
97 	{
98 		atomic_add(&fRefCount, 1);
99 	}
100 
101 	void ReleaseReference()
102 	{
103 		if (atomic_add(&fRefCount, -1) == 1)
104 			delete this;
105 	}
106 
107 	bool HasPermissions() const
108 	{
109 		if ((fPermissions & S_IWOTH) != 0)
110 			return true;
111 
112 		uid_t uid = geteuid();
113 		if (uid == 0 || (uid == fUID && (fPermissions & S_IWUSR) != 0))
114 			return true;
115 
116 		gid_t gid = getegid();
117 		if (gid == fGID && (fPermissions & S_IWGRP) != 0)
118 			return true;
119 
120 		return false;
121 	}
122 
123 	virtual sem_id ID() const
124 	{
125 		return SemaphoreID();
126 	}
127 
128 	virtual SemInfo* Clone()
129 	{
130 		AcquireReference();
131 		return this;
132 	}
133 
134 	virtual void Delete()
135 	{
136 		ReleaseReference();
137 	}
138 
139 	NamedSem*& HashLink()
140 	{
141 		return fHashLink;
142 	}
143 
144 private:
145 	char*		fName;
146 	int32		fRefCount;
147 	uid_t		fUID;
148 	gid_t		fGID;
149 	mode_t		fPermissions;
150 
151 	NamedSem*	fHashLink;
152 };
153 
154 
155 struct NamedSemHashDefinition {
156 	typedef const char*	KeyType;
157 	typedef NamedSem	ValueType;
158 
159 	size_t HashKey(const KeyType& key) const
160 	{
161 		return hash_hash_string(key);
162 	}
163 
164 	size_t Hash(NamedSem* semaphore) const
165 	{
166 		return HashKey(semaphore->Name());
167 	}
168 
169 	bool Compare(const KeyType& key, NamedSem* semaphore) const
170 	{
171 		return strcmp(key, semaphore->Name()) == 0;
172 	}
173 
174 	NamedSem*& GetLink(NamedSem* semaphore) const
175 	{
176 		return semaphore->HashLink();
177 	}
178 };
179 
180 
181 class GlobalSemTable {
182 public:
183 	GlobalSemTable()
184 		:
185 		fSemaphoreCount(0)
186 	{
187 		mutex_init(&fLock, "global named sem table");
188 	}
189 
190 	~GlobalSemTable()
191 	{
192 		mutex_destroy(&fLock);
193 	}
194 
195 	status_t Init()
196 	{
197 		return fNamedSemaphores.Init();
198 	}
199 
200 	status_t OpenNamedSem(const char* name, int openFlags, mode_t mode,
201 		uint32 semCount, NamedSem*& _sem, bool& _created)
202 	{
203 		MutexLocker _(fLock);
204 
205 		NamedSem* sem = fNamedSemaphores.Lookup(name);
206 		if (sem != NULL) {
207 			if ((openFlags & O_EXCL) != 0)
208 				return EEXIST;
209 
210 			if (!sem->HasPermissions())
211 				return EACCES;
212 
213 			sem->AcquireReference();
214 			_sem = sem;
215 			_created = false;
216 			return B_OK;
217 		}
218 
219 		if ((openFlags & O_CREAT) == 0)
220 			return ENOENT;
221 
222 		// does not exist yet -- create
223 		if (fSemaphoreCount >= MAX_POSIX_SEMS)
224 			return ENOSPC;
225 
226 		sem = new(std::nothrow) NamedSem;
227 		if (sem == NULL)
228 			return B_NO_MEMORY;
229 
230 		status_t error = sem->Init(name, mode, semCount);
231 		if (error != B_OK) {
232 			delete sem;
233 			return error;
234 		}
235 
236 		error = fNamedSemaphores.Insert(sem);
237 		if (error != B_OK) {
238 			delete sem;
239 			return error;
240 		}
241 
242 		// add one reference for the table
243 		sem->AcquireReference();
244 
245 		fSemaphoreCount++;
246 
247 		_sem = sem;
248 		_created = true;
249 		return B_OK;
250 	}
251 
252 	status_t UnlinkNamedSem(const char* name)
253 	{
254 		MutexLocker _(fLock);
255 
256 		NamedSem* sem = fNamedSemaphores.Lookup(name);
257 		if (sem == NULL)
258 			return ENOENT;
259 
260 		if (!sem->HasPermissions())
261 			return EACCES;
262 
263 		fNamedSemaphores.Remove(sem);
264 		sem->ReleaseReference();
265 			// release the table reference
266 		fSemaphoreCount--;
267 
268 		return B_OK;
269 	}
270 
271 private:
272 	typedef BOpenHashTable<NamedSemHashDefinition, true> NamedSemTable;
273 
274 	mutex			fLock;
275 	NamedSemTable	fNamedSemaphores;
276 	int32			fSemaphoreCount;
277 };
278 
279 
280 static GlobalSemTable sSemTable;
281 
282 
283 class TeamSemInfo {
284 public:
285 	TeamSemInfo(SemInfo* semaphore, sem_t* userSem)
286 		:
287 		fSemaphore(semaphore),
288 		fUserSemaphore(userSem),
289 		fOpenCount(1)
290 	{
291 	}
292 
293 	~TeamSemInfo()
294 	{
295 		if (fSemaphore != NULL)
296 			fSemaphore->Delete();
297 	}
298 
299 	sem_id ID() const				{ return fSemaphore->ID(); }
300 	sem_id SemaphoreID() const		{ return fSemaphore->SemaphoreID(); }
301 	sem_t* UserSemaphore() const	{ return fUserSemaphore; }
302 
303 	void Open()
304 	{
305 		fOpenCount++;
306 	}
307 
308 	bool Close()
309 	{
310 		return --fOpenCount == 0;
311 	}
312 
313 	TeamSemInfo* Clone() const
314 	{
315 		SemInfo* sem = fSemaphore->Clone();
316 		if (sem == NULL)
317 			return NULL;
318 
319 		TeamSemInfo* clone = new(std::nothrow) TeamSemInfo(sem, fUserSemaphore);
320 		if (clone == NULL) {
321 			sem->Delete();
322 			return NULL;
323 		}
324 
325 		clone->fOpenCount = fOpenCount;
326 
327 		return clone;
328 	}
329 
330 	TeamSemInfo*& HashLink()
331 	{
332 		return fHashLink;
333 	}
334 
335 private:
336 	SemInfo*		fSemaphore;
337 	sem_t*			fUserSemaphore;
338 	int32			fOpenCount;
339 
340 	TeamSemInfo*	fHashLink;
341 };
342 
343 
344 struct TeamSemHashDefinition {
345 	typedef sem_id		KeyType;
346 	typedef TeamSemInfo	ValueType;
347 
348 	size_t HashKey(const KeyType& key) const
349 	{
350 		return (size_t)key;
351 	}
352 
353 	size_t Hash(TeamSemInfo* semaphore) const
354 	{
355 		return HashKey(semaphore->ID());
356 	}
357 
358 	bool Compare(const KeyType& key, TeamSemInfo* semaphore) const
359 	{
360 		return key == semaphore->ID();
361 	}
362 
363 	TeamSemInfo*& GetLink(TeamSemInfo* semaphore) const
364 	{
365 		return semaphore->HashLink();
366 	}
367 };
368 
369 } // namespace
370 
371 
372 struct realtime_sem_context {
373 	realtime_sem_context()
374 		:
375 		fSemaphoreCount(0)
376 	{
377 		mutex_init(&fLock, "realtime sem context");
378 	}
379 
380 	~realtime_sem_context()
381 	{
382 		mutex_lock(&fLock);
383 
384 		// delete all semaphores.
385 		SemTable::Iterator it = fSemaphores.GetIterator();
386 		while (TeamSemInfo* sem = it.Next()) {
387 			// Note, this uses internal knowledge about how the iterator works.
388 			// Ugly, but there's no good alternative.
389 			fSemaphores.RemoveUnchecked(sem);
390 			delete sem;
391 		}
392 
393 		mutex_destroy(&fLock);
394 	}
395 
396 	status_t Init()
397 	{
398 		fNextPrivateSemID = -1;
399 		return fSemaphores.Init();
400 	}
401 
402 	realtime_sem_context* Clone()
403 	{
404 		// create new context
405 		realtime_sem_context* context = new(std::nothrow) realtime_sem_context;
406 		if (context == NULL)
407 			return NULL;
408 		ObjectDeleter<realtime_sem_context> contextDeleter(context);
409 
410 		MutexLocker _(fLock);
411 
412 		context->fNextPrivateSemID = fNextPrivateSemID;
413 
414 		// clone all semaphores
415 		SemTable::Iterator it = fSemaphores.GetIterator();
416 		while (TeamSemInfo* sem = it.Next()) {
417 			TeamSemInfo* clonedSem = sem->Clone();
418 			if (clonedSem == NULL)
419 				return NULL;
420 
421 			if (context->fSemaphores.Insert(clonedSem) != B_OK) {
422 				delete clonedSem;
423 				return NULL;
424 			}
425 			context->fSemaphoreCount++;
426 		}
427 
428 		contextDeleter.Detach();
429 		return context;
430 	}
431 
432 	status_t OpenSem(const char* name, int openFlags, mode_t mode,
433 		uint32 semCount, sem_t* userSem, sem_t*& _usedUserSem, int32_t& _id,
434 		bool& _created)
435 	{
436 		NamedSem* sem = NULL;
437 		status_t error = sSemTable.OpenNamedSem(name, openFlags, mode, semCount,
438 			sem, _created);
439 		if (error != B_OK)
440 			return error;
441 
442 		MutexLocker _(fLock);
443 
444 		TeamSemInfo* teamSem = fSemaphores.Lookup(sem->ID());
445 		if (teamSem != NULL) {
446 			// already open -- just increment the open count
447 			teamSem->Open();
448 			sem->ReleaseReference();
449 			_usedUserSem = teamSem->UserSemaphore();
450 			_id = teamSem->ID();
451 			return B_OK;
452 		}
453 
454 		// not open yet -- create a new team sem
455 
456 		// first check the semaphore limit, though
457 		if (fSemaphoreCount >= MAX_POSIX_SEMS_PER_TEAM) {
458 			sem->ReleaseReference();
459 			if (_created)
460 				sSemTable.UnlinkNamedSem(name);
461 			return ENOSPC;
462 		}
463 
464 		teamSem = new(std::nothrow) TeamSemInfo(sem, userSem);
465 		if (teamSem == NULL) {
466 			sem->ReleaseReference();
467 			if (_created)
468 				sSemTable.UnlinkNamedSem(name);
469 			return B_NO_MEMORY;
470 		}
471 
472 		error = fSemaphores.Insert(teamSem);
473 		if (error != B_OK) {
474 			delete teamSem;
475 			if (_created)
476 				sSemTable.UnlinkNamedSem(name);
477 			return error;
478 		}
479 
480 		fSemaphoreCount++;
481 
482 		_usedUserSem = teamSem->UserSemaphore();
483 		_id = teamSem->ID();
484 
485 		return B_OK;
486 	}
487 
488 	status_t CloseSem(sem_id id, sem_t*& deleteUserSem)
489 	{
490 		deleteUserSem = NULL;
491 
492 		MutexLocker _(fLock);
493 
494 		TeamSemInfo* sem = fSemaphores.Lookup(id);
495 		if (sem == NULL)
496 			return B_BAD_VALUE;
497 
498 		if (sem->Close()) {
499 			// last reference closed
500 			fSemaphores.Remove(sem);
501 			fSemaphoreCount--;
502 			deleteUserSem = sem->UserSemaphore();
503 			delete sem;
504 		}
505 
506 		return B_OK;
507 	}
508 
509 	status_t AcquireSem(sem_id id, bigtime_t timeout)
510 	{
511 		MutexLocker locker(fLock);
512 
513 		TeamSemInfo* sem = fSemaphores.Lookup(id);
514 		if (sem == NULL)
515 			return B_BAD_VALUE;
516 		else
517 			id = sem->SemaphoreID();
518 
519 		locker.Unlock();
520 
521 		status_t error;
522 		if (timeout == 0) {
523 			error = acquire_sem_etc(id, 1, B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT,
524 				0);
525 		} else if (timeout == B_INFINITE_TIMEOUT) {
526 			error = acquire_sem_etc(id, 1, B_CAN_INTERRUPT, 0);
527 		} else {
528 			error = acquire_sem_etc(id, 1,
529 				B_CAN_INTERRUPT | B_ABSOLUTE_REAL_TIME_TIMEOUT, timeout);
530 		}
531 
532 		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
533 	}
534 
535 	status_t ReleaseSem(sem_id id)
536 	{
537 		MutexLocker locker(fLock);
538 
539 		TeamSemInfo* sem = fSemaphores.Lookup(id);
540 		if (sem == NULL)
541 			return B_BAD_VALUE;
542 		else
543 			id = sem->SemaphoreID();
544 
545 		locker.Unlock();
546 
547 		status_t error = release_sem(id);
548 		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
549 	}
550 
551 	status_t GetSemCount(sem_id id, int& _count)
552 	{
553 		MutexLocker locker(fLock);
554 
555 		TeamSemInfo* sem = fSemaphores.Lookup(id);
556 		if (sem == NULL)
557 				return B_BAD_VALUE;
558 		else
559 			id = sem->SemaphoreID();
560 
561 		locker.Unlock();
562 
563 		int32 count;
564 		status_t error = get_sem_count(id, &count);
565 		if (error != B_OK)
566 			return error;
567 
568 		_count = count;
569 		return B_OK;
570 	}
571 
572 private:
573 	sem_id _NextPrivateSemID()
574 	{
575 		while (true) {
576 			if (fNextPrivateSemID >= 0)
577 				fNextPrivateSemID = -1;
578 
579 			sem_id id = fNextPrivateSemID--;
580 			if (fSemaphores.Lookup(id) == NULL)
581 				return id;
582 		}
583 	}
584 
585 private:
586 	typedef BOpenHashTable<TeamSemHashDefinition, true> SemTable;
587 
588 	mutex		fLock;
589 	SemTable	fSemaphores;
590 	int32		fSemaphoreCount;
591 	sem_id		fNextPrivateSemID;
592 };
593 
594 
595 // #pragma mark - implementation private
596 
597 
598 static realtime_sem_context*
599 get_current_team_context()
600 {
601 	Team* team = thread_get_current_thread()->team;
602 
603 	// get context
604 	realtime_sem_context* context = atomic_pointer_get(
605 		&team->realtime_sem_context);
606 	if (context != NULL)
607 		return context;
608 
609 	// no context yet -- create a new one
610 	context = new(std::nothrow) realtime_sem_context;
611 	if (context == NULL || context->Init() != B_OK) {
612 		delete context;
613 		return NULL;
614 	}
615 
616 	// set the allocated context
617 	realtime_sem_context* oldContext = atomic_pointer_test_and_set(
618 		&team->realtime_sem_context, context, (realtime_sem_context*)NULL);
619 	if (oldContext == NULL)
620 		return context;
621 
622 	// someone else was quicker
623 	delete context;
624 	return oldContext;
625 }
626 
627 
628 static status_t
629 copy_sem_name_to_kernel(const char* userName, KPath& buffer, char*& name)
630 {
631 	if (userName == NULL)
632 		return B_BAD_VALUE;
633 	if (!IS_USER_ADDRESS(userName))
634 		return B_BAD_ADDRESS;
635 
636 	if (buffer.InitCheck() != B_OK)
637 		return B_NO_MEMORY;
638 
639 	// copy userland path to kernel
640 	name = buffer.LockBuffer();
641 	ssize_t actualLength = user_strlcpy(name, userName, buffer.BufferSize());
642 
643 	if (actualLength < 0)
644 		return B_BAD_ADDRESS;
645 	if ((size_t)actualLength >= buffer.BufferSize())
646 		return ENAMETOOLONG;
647 
648 	return B_OK;
649 }
650 
651 
652 // #pragma mark - kernel internal
653 
654 
655 void
656 realtime_sem_init()
657 {
658 	new(&sSemTable) GlobalSemTable;
659 	if (sSemTable.Init() != B_OK)
660 		panic("realtime_sem_init() failed to init global sem table");
661 }
662 
663 
664 void
665 delete_realtime_sem_context(realtime_sem_context* context)
666 {
667 	delete context;
668 }
669 
670 
671 realtime_sem_context*
672 clone_realtime_sem_context(realtime_sem_context* context)
673 {
674 	if (context == NULL)
675 		return NULL;
676 
677 	return context->Clone();
678 }
679 
680 
681 // #pragma mark - syscalls
682 
683 
684 status_t
685 _user_realtime_sem_open(const char* userName, int openFlagsOrShared,
686 	mode_t mode, uint32 semCount, sem_t* userSem, sem_t** _usedUserSem)
687 {
688 	realtime_sem_context* context = get_current_team_context();
689 	if (context == NULL)
690 		return B_NO_MEMORY;
691 
692 	if (semCount > MAX_POSIX_SEM_VALUE)
693 		return B_BAD_VALUE;
694 
695 	// userSem must always be given
696 	if (userSem == NULL)
697 		return B_BAD_VALUE;
698 	if (!IS_USER_ADDRESS(userSem))
699 		return B_BAD_ADDRESS;
700 
701 	// check user pointers
702 	if (_usedUserSem == NULL)
703 		return B_BAD_VALUE;
704 	if (!IS_USER_ADDRESS(_usedUserSem) || !IS_USER_ADDRESS(userName))
705 		return B_BAD_ADDRESS;
706 
707 	// copy name to kernel
708 	KPath nameBuffer(B_PATH_NAME_LENGTH);
709 	char* name;
710 	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
711 	if (error != B_OK)
712 		return error;
713 
714 	// open the semaphore
715 	sem_t* usedUserSem;
716 	bool created = false;
717 	int32_t id;
718 	error = context->OpenSem(name, openFlagsOrShared, mode, semCount, userSem,
719 		usedUserSem, id, created);
720 	if (error != B_OK)
721 		return error;
722 
723 	// copy results back to userland
724 	if (user_memcpy(&userSem->u.named_sem_id, &id, sizeof(int32_t)) != B_OK
725 		|| user_memcpy(_usedUserSem, &usedUserSem, sizeof(sem_t*)) != B_OK) {
726 		if (created)
727 			sSemTable.UnlinkNamedSem(name);
728 		sem_t* dummy;
729 		context->CloseSem(id, dummy);
730 		return B_BAD_ADDRESS;
731 	}
732 
733 	return B_OK;
734 }
735 
736 
737 status_t
738 _user_realtime_sem_close(sem_id semID, sem_t** _deleteUserSem)
739 {
740 	if (_deleteUserSem != NULL && !IS_USER_ADDRESS(_deleteUserSem))
741 		return B_BAD_ADDRESS;
742 
743 	realtime_sem_context* context = get_current_team_context();
744 	if (context == NULL)
745 		return B_BAD_VALUE;
746 
747 	// close sem
748 	sem_t* deleteUserSem;
749 	status_t error = context->CloseSem(semID, deleteUserSem);
750 	if (error != B_OK)
751 		return error;
752 
753 	// copy back result to userland
754 	if (_deleteUserSem != NULL
755 		&& user_memcpy(_deleteUserSem, &deleteUserSem, sizeof(sem_t*))
756 			!= B_OK) {
757 		return B_BAD_ADDRESS;
758 	}
759 
760 	return B_OK;
761 }
762 
763 
764 status_t
765 _user_realtime_sem_unlink(const char* userName)
766 {
767 	// copy name to kernel
768 	KPath nameBuffer(B_PATH_NAME_LENGTH);
769 	char* name;
770 	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
771 	if (error != B_OK)
772 		return error;
773 
774 	return sSemTable.UnlinkNamedSem(name);
775 }
776 
777 
778 status_t
779 _user_realtime_sem_get_value(sem_id semID, int* _value)
780 {
781 	if (_value == NULL)
782 		return B_BAD_VALUE;
783 	if (!IS_USER_ADDRESS(_value))
784 		return B_BAD_ADDRESS;
785 
786 	realtime_sem_context* context = get_current_team_context();
787 	if (context == NULL)
788 		return B_BAD_VALUE;
789 
790 	// get sem count
791 	int count;
792 	status_t error = context->GetSemCount(semID, count);
793 	if (error != B_OK)
794 		return error;
795 
796 	// copy back result to userland
797 	if (user_memcpy(_value, &count, sizeof(int)) != B_OK)
798 		return B_BAD_ADDRESS;
799 
800 	return B_OK;
801 }
802 
803 
804 status_t
805 _user_realtime_sem_post(sem_id semID)
806 {
807 	realtime_sem_context* context = get_current_team_context();
808 	if (context == NULL)
809 		return B_BAD_VALUE;
810 
811 	return context->ReleaseSem(semID);
812 }
813 
814 
815 status_t
816 _user_realtime_sem_wait(sem_id semID, bigtime_t timeout)
817 {
818 	realtime_sem_context* context = get_current_team_context();
819 	if (context == NULL)
820 		return B_BAD_VALUE;
821 
822 	return syscall_restart_handle_post(context->AcquireSem(semID, timeout));
823 }
824