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
27namespace {
28
29class SemInfo {
30public:
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
58private:
59	sem_id	fSemaphoreID;
60};
61
62
63class NamedSem : public SemInfo {
64public:
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
144private:
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
155struct 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
181class GlobalSemTable {
182public:
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
271private:
272	typedef BOpenHashTable<NamedSemHashDefinition, true> NamedSemTable;
273
274	mutex			fLock;
275	NamedSemTable	fNamedSemaphores;
276	int32			fSemaphoreCount;
277};
278
279
280static GlobalSemTable sSemTable;
281
282
283class TeamSemInfo {
284public:
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
335private:
336	SemInfo*		fSemaphore;
337	sem_t*			fUserSemaphore;
338	int32			fOpenCount;
339
340	TeamSemInfo*	fHashLink;
341};
342
343
344struct 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
372struct 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
562private:
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
575private:
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
588static realtime_sem_context*
589get_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
618static status_t
619copy_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
645void
646realtime_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
654void
655delete_realtime_sem_context(realtime_sem_context* context)
656{
657	delete context;
658}
659
660
661realtime_sem_context*
662clone_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
674status_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
727status_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
754status_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
768status_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
794status_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
805status_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