xref: /haiku/src/system/kernel/posix/realtime_sem.cpp (revision 52f7c9389475e19fc21487b38064b4390eeb6fea)
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, uint32 flags, 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 = acquire_sem_etc(id, 1, flags | B_CAN_INTERRUPT, timeout);
522 		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
523 	}
524 
525 	status_t ReleaseSem(sem_id id)
526 	{
527 		MutexLocker locker(fLock);
528 
529 		TeamSemInfo* sem = fSemaphores.Lookup(id);
530 		if (sem == NULL)
531 			return B_BAD_VALUE;
532 		else
533 			id = sem->SemaphoreID();
534 
535 		locker.Unlock();
536 
537 		status_t error = release_sem(id);
538 		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
539 	}
540 
541 	status_t GetSemCount(sem_id id, int& _count)
542 	{
543 		MutexLocker locker(fLock);
544 
545 		TeamSemInfo* sem = fSemaphores.Lookup(id);
546 		if (sem == NULL)
547 				return B_BAD_VALUE;
548 		else
549 			id = sem->SemaphoreID();
550 
551 		locker.Unlock();
552 
553 		int32 count;
554 		status_t error = get_sem_count(id, &count);
555 		if (error != B_OK)
556 			return error;
557 
558 		_count = count;
559 		return B_OK;
560 	}
561 
562 private:
563 	sem_id _NextPrivateSemID()
564 	{
565 		while (true) {
566 			if (fNextPrivateSemID >= 0)
567 				fNextPrivateSemID = -1;
568 
569 			sem_id id = fNextPrivateSemID--;
570 			if (fSemaphores.Lookup(id) == NULL)
571 				return id;
572 		}
573 	}
574 
575 private:
576 	typedef BOpenHashTable<TeamSemHashDefinition, true> SemTable;
577 
578 	mutex		fLock;
579 	SemTable	fSemaphores;
580 	int32		fSemaphoreCount;
581 	sem_id		fNextPrivateSemID;
582 };
583 
584 
585 // #pragma mark - implementation private
586 
587 
588 static realtime_sem_context*
589 get_current_team_context()
590 {
591 	Team* team = thread_get_current_thread()->team;
592 
593 	// get context
594 	realtime_sem_context* context = atomic_pointer_get(
595 		&team->realtime_sem_context);
596 	if (context != NULL)
597 		return context;
598 
599 	// no context yet -- create a new one
600 	context = new(std::nothrow) realtime_sem_context;
601 	if (context == NULL || context->Init() != B_OK) {
602 		delete context;
603 		return NULL;
604 	}
605 
606 	// set the allocated context
607 	realtime_sem_context* oldContext = atomic_pointer_test_and_set(
608 		&team->realtime_sem_context, context, (realtime_sem_context*)NULL);
609 	if (oldContext == NULL)
610 		return context;
611 
612 	// someone else was quicker
613 	delete context;
614 	return oldContext;
615 }
616 
617 
618 static status_t
619 copy_sem_name_to_kernel(const char* userName, KPath& buffer, char*& name)
620 {
621 	if (userName == NULL)
622 		return B_BAD_VALUE;
623 	if (!IS_USER_ADDRESS(userName))
624 		return B_BAD_ADDRESS;
625 
626 	if (buffer.InitCheck() != B_OK)
627 		return B_NO_MEMORY;
628 
629 	// copy userland path to kernel
630 	name = buffer.LockBuffer();
631 	ssize_t actualLength = user_strlcpy(name, userName, buffer.BufferSize());
632 
633 	if (actualLength < 0)
634 		return B_BAD_ADDRESS;
635 	if ((size_t)actualLength >= buffer.BufferSize())
636 		return ENAMETOOLONG;
637 
638 	return B_OK;
639 }
640 
641 
642 // #pragma mark - kernel internal
643 
644 
645 void
646 realtime_sem_init()
647 {
648 	new(&sSemTable) GlobalSemTable;
649 	if (sSemTable.Init() != B_OK)
650 		panic("realtime_sem_init() failed to init global sem table");
651 }
652 
653 
654 void
655 delete_realtime_sem_context(realtime_sem_context* context)
656 {
657 	delete context;
658 }
659 
660 
661 realtime_sem_context*
662 clone_realtime_sem_context(realtime_sem_context* context)
663 {
664 	if (context == NULL)
665 		return NULL;
666 
667 	return context->Clone();
668 }
669 
670 
671 // #pragma mark - syscalls
672 
673 
674 status_t
675 _user_realtime_sem_open(const char* userName, int openFlagsOrShared,
676 	mode_t mode, uint32 semCount, sem_t* userSem, sem_t** _usedUserSem)
677 {
678 	realtime_sem_context* context = get_current_team_context();
679 	if (context == NULL)
680 		return B_NO_MEMORY;
681 
682 	if (semCount > MAX_POSIX_SEM_VALUE)
683 		return B_BAD_VALUE;
684 
685 	// userSem must always be given
686 	if (userSem == NULL)
687 		return B_BAD_VALUE;
688 	if (!IS_USER_ADDRESS(userSem))
689 		return B_BAD_ADDRESS;
690 
691 	// check user pointers
692 	if (_usedUserSem == NULL)
693 		return B_BAD_VALUE;
694 	if (!IS_USER_ADDRESS(_usedUserSem) || !IS_USER_ADDRESS(userName))
695 		return B_BAD_ADDRESS;
696 
697 	// copy name to kernel
698 	KPath nameBuffer(B_PATH_NAME_LENGTH);
699 	char* name;
700 	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
701 	if (error != B_OK)
702 		return error;
703 
704 	// open the semaphore
705 	sem_t* usedUserSem;
706 	bool created = false;
707 	int32_t id;
708 	error = context->OpenSem(name, openFlagsOrShared, mode, semCount, userSem,
709 		usedUserSem, id, created);
710 	if (error != B_OK)
711 		return error;
712 
713 	// copy results back to userland
714 	if (user_memcpy(&userSem->u.named_sem_id, &id, sizeof(int32_t)) != B_OK
715 		|| user_memcpy(_usedUserSem, &usedUserSem, sizeof(sem_t*)) != B_OK) {
716 		if (created)
717 			sSemTable.UnlinkNamedSem(name);
718 		sem_t* dummy;
719 		context->CloseSem(id, dummy);
720 		return B_BAD_ADDRESS;
721 	}
722 
723 	return B_OK;
724 }
725 
726 
727 status_t
728 _user_realtime_sem_close(sem_id semID, sem_t** _deleteUserSem)
729 {
730 	if (_deleteUserSem != NULL && !IS_USER_ADDRESS(_deleteUserSem))
731 		return B_BAD_ADDRESS;
732 
733 	realtime_sem_context* context = get_current_team_context();
734 	if (context == NULL)
735 		return B_BAD_VALUE;
736 
737 	// close sem
738 	sem_t* deleteUserSem;
739 	status_t error = context->CloseSem(semID, deleteUserSem);
740 	if (error != B_OK)
741 		return error;
742 
743 	// copy back result to userland
744 	if (_deleteUserSem != NULL
745 		&& user_memcpy(_deleteUserSem, &deleteUserSem, sizeof(sem_t*))
746 			!= B_OK) {
747 		return B_BAD_ADDRESS;
748 	}
749 
750 	return B_OK;
751 }
752 
753 
754 status_t
755 _user_realtime_sem_unlink(const char* userName)
756 {
757 	// copy name to kernel
758 	KPath nameBuffer(B_PATH_NAME_LENGTH);
759 	char* name;
760 	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
761 	if (error != B_OK)
762 		return error;
763 
764 	return sSemTable.UnlinkNamedSem(name);
765 }
766 
767 
768 status_t
769 _user_realtime_sem_get_value(sem_id semID, int* _value)
770 {
771 	if (_value == NULL)
772 		return B_BAD_VALUE;
773 	if (!IS_USER_ADDRESS(_value))
774 		return B_BAD_ADDRESS;
775 
776 	realtime_sem_context* context = get_current_team_context();
777 	if (context == NULL)
778 		return B_BAD_VALUE;
779 
780 	// get sem count
781 	int count;
782 	status_t error = context->GetSemCount(semID, count);
783 	if (error != B_OK)
784 		return error;
785 
786 	// copy back result to userland
787 	if (user_memcpy(_value, &count, sizeof(int)) != B_OK)
788 		return B_BAD_ADDRESS;
789 
790 	return B_OK;
791 }
792 
793 
794 status_t
795 _user_realtime_sem_post(sem_id semID)
796 {
797 	realtime_sem_context* context = get_current_team_context();
798 	if (context == NULL)
799 		return B_BAD_VALUE;
800 
801 	return context->ReleaseSem(semID);
802 }
803 
804 
805 status_t
806 _user_realtime_sem_wait(sem_id semID, uint32 flags, bigtime_t timeout)
807 {
808 	realtime_sem_context* context = get_current_team_context();
809 	if (context == NULL)
810 		return B_BAD_VALUE;
811 
812 	return syscall_restart_handle_post(context->AcquireSem(semID, flags, timeout));
813 }
814