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