/*
 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
 
#include <posix/realtime_sem.h>
 
#include <string.h>
 
#include <new>
 
#include <OS.h>
 
#include <AutoDeleter.h>
#include <fs/KPath.h>
#include <kernel.h>
#include <lock.h>
#include <syscall_restart.h>
#include <team.h>
#include <thread.h>
#include <util/atomic.h>
#include <util/AutoLock.h>
#include <util/OpenHashTable.h>
#include <util/StringHash.h>
 
 
namespace {
 
class SemInfo {
public:
	SemInfo()
		:
		fSemaphoreID(-1)
	{
	}
 
	virtual ~SemInfo()
	{
		if (fSemaphoreID >= 0)
			delete_sem(fSemaphoreID);
	}
 
	sem_id SemaphoreID() const			{ return fSemaphoreID; }
 
	status_t Init(int32 semCount, const char* name)
	{
		fSemaphoreID = create_sem(semCount, name);
		if (fSemaphoreID < 0)
			return fSemaphoreID;
 
		return B_OK;
	}
 
	virtual sem_id ID() const = 0;
	virtual SemInfo* Clone() = 0;
	virtual void Delete() = 0;
 
private:
	sem_id	fSemaphoreID;
};
 
 
class NamedSem : public SemInfo {
public:
	NamedSem()
		:
		fName(NULL),
		fRefCount(1)
	{
	}
 
	virtual ~NamedSem()
	{
		free(fName);
	}
 
	const char* Name() const		{ return fName; }
 
	status_t Init(const char* name, mode_t mode, int32 semCount)
	{
		status_t error = SemInfo::Init(semCount, name);
		if (error != B_OK)
			return error;
 
		fName = strdup(name);
		if (fName == NULL)
			return B_NO_MEMORY;
 
		fUID = geteuid();
		fGID = getegid();
		fPermissions = mode;
 
		return B_OK;
	}
 
	void AcquireReference()
	{
		atomic_add(&fRefCount, 1);
	}
 
	void ReleaseReference()
	{
		if (atomic_add(&fRefCount, -1) == 1)
			delete this;
	}
 
	bool HasPermissions() const
	{
		if ((fPermissions & S_IWOTH) != 0)
			return true;
 
		uid_t uid = geteuid();
		if (uid == 0 || (uid == fUID && (fPermissions & S_IWUSR) != 0))
			return true;
 
		gid_t gid = getegid();
		if (gid == fGID && (fPermissions & S_IWGRP) != 0)
			return true;
 
		return false;
	}
 
	virtual sem_id ID() const
	{
		return SemaphoreID();
	}
 
	virtual SemInfo* Clone()
	{
		AcquireReference();
		return this;
	}
 
	virtual void Delete()
	{
		ReleaseReference();
	}
 
	NamedSem*& HashLink()
	{
		return fHashLink;
	}
 
private:
	char*		fName;
	int32		fRefCount;
	uid_t		fUID;
	gid_t		fGID;
	mode_t		fPermissions;
 
	NamedSem*	fHashLink;
};
 
 
struct NamedSemHashDefinition {
	typedef const char*	KeyType;
	typedef NamedSem	ValueType;
 
	size_t HashKey(const KeyType& key) const
	{
		return hash_hash_string(key);
	}
 
	size_t Hash(NamedSem* semaphore) const
	{
		return HashKey(semaphore->Name());
	}
 
	bool Compare(const KeyType& key, NamedSem* semaphore) const
	{
		return strcmp(key, semaphore->Name()) == 0;
	}
 
	NamedSem*& GetLink(NamedSem* semaphore) const
	{
		return semaphore->HashLink();
	}
};
 
 
class GlobalSemTable {
public:
	GlobalSemTable()
		:
		fSemaphoreCount(0)
	{
		mutex_init(&fLock, "global named sem table");
	}
 
	~GlobalSemTable()
	{
		mutex_destroy(&fLock);
	}
 
	status_t Init()
	{
		return fNamedSemaphores.Init();
	}
 
	status_t OpenNamedSem(const char* name, int openFlags, mode_t mode,
		uint32 semCount, NamedSem*& _sem, bool& _created)
	{
		MutexLocker _(fLock);
 
		NamedSem* sem = fNamedSemaphores.Lookup(name);
		if (sem != NULL) {
			if ((openFlags & O_EXCL) != 0)
				return EEXIST;
 
			if (!sem->HasPermissions())
				return EACCES;
 
			sem->AcquireReference();
			_sem = sem;
			_created = false;
			return B_OK;
		}
 
		if ((openFlags & O_CREAT) == 0)
			return ENOENT;
 
		// does not exist yet -- create
		if (fSemaphoreCount >= MAX_POSIX_SEMS)
			return ENOSPC;
 
		sem = new(std::nothrow) NamedSem;
		if (sem == NULL)
			return B_NO_MEMORY;
 
		status_t error = sem->Init(name, mode, semCount);
		if (error != B_OK) {
			delete sem;
			return error;
		}
 
		error = fNamedSemaphores.Insert(sem);
		if (error != B_OK) {
			delete sem;
			return error;
		}
 
		// add one reference for the table
		sem->AcquireReference();
 
		fSemaphoreCount++;
 
		_sem = sem;
		_created = true;
		return B_OK;
	}
 
	status_t UnlinkNamedSem(const char* name)
	{
		MutexLocker _(fLock);
 
		NamedSem* sem = fNamedSemaphores.Lookup(name);
		if (sem == NULL)
			return ENOENT;
 
		if (!sem->HasPermissions())
			return EACCES;
 
		fNamedSemaphores.Remove(sem);
		sem->ReleaseReference();
			// release the table reference
		fSemaphoreCount--;
 
		return B_OK;
	}
 
private:
	typedef BOpenHashTable<NamedSemHashDefinition, true> NamedSemTable;
 
	mutex			fLock;
	NamedSemTable	fNamedSemaphores;
	int32			fSemaphoreCount;
};
 
 
static GlobalSemTable sSemTable;
 
 
class TeamSemInfo {
public:
	TeamSemInfo(SemInfo* semaphore, sem_t* userSem)
		:
		fSemaphore(semaphore),
		fUserSemaphore(userSem),
		fOpenCount(1)
	{
	}
 
	~TeamSemInfo()
	{
		if (fSemaphore != NULL)
			fSemaphore->Delete();
	}
 
	sem_id ID() const				{ return fSemaphore->ID(); }
	sem_id SemaphoreID() const		{ return fSemaphore->SemaphoreID(); }
	sem_t* UserSemaphore() const	{ return fUserSemaphore; }
 
	void Open()
	{
		fOpenCount++;
	}
 
	bool Close()
	{
		return --fOpenCount == 0;
	}
 
	TeamSemInfo* Clone() const
	{
		SemInfo* sem = fSemaphore->Clone();
		if (sem == NULL)
			return NULL;
 
		TeamSemInfo* clone = new(std::nothrow) TeamSemInfo(sem, fUserSemaphore);
		if (clone == NULL) {
			sem->Delete();
			return NULL;
		}
 
		clone->fOpenCount = fOpenCount;
 
		return clone;
	}
 
	TeamSemInfo*& HashLink()
	{
		return fHashLink;
	}
 
private:
	SemInfo*		fSemaphore;
	sem_t*			fUserSemaphore;
	int32			fOpenCount;
 
	TeamSemInfo*	fHashLink;
};
 
 
struct TeamSemHashDefinition {
	typedef sem_id		KeyType;
	typedef TeamSemInfo	ValueType;
 
	size_t HashKey(const KeyType& key) const
	{
		return (size_t)key;
	}
 
	size_t Hash(TeamSemInfo* semaphore) const
	{
		return HashKey(semaphore->ID());
	}
 
	bool Compare(const KeyType& key, TeamSemInfo* semaphore) const
	{
		return key == semaphore->ID();
	}
 
	TeamSemInfo*& GetLink(TeamSemInfo* semaphore) const
	{
		return semaphore->HashLink();
	}
};
 
} // namespace
 
 
struct realtime_sem_context {
	realtime_sem_context()
		:
		fSemaphoreCount(0)
	{
		mutex_init(&fLock, "realtime sem context");
	}
 
	~realtime_sem_context()
	{
		mutex_lock(&fLock);
 
		// delete all semaphores.
		SemTable::Iterator it = fSemaphores.GetIterator();
		while (TeamSemInfo* sem = it.Next()) {
			// Note, this uses internal knowledge about how the iterator works.
			// Ugly, but there's no good alternative.
			fSemaphores.RemoveUnchecked(sem);
			delete sem;
		}
 
		mutex_destroy(&fLock);
	}
 
	status_t Init()
	{
		fNextPrivateSemID = -1;
		return fSemaphores.Init();
	}
 
	realtime_sem_context* Clone()
	{
		// create new context
		realtime_sem_context* context = new(std::nothrow) realtime_sem_context;
		if (context == NULL)
			return NULL;
		ObjectDeleter<realtime_sem_context> contextDeleter(context);
 
		MutexLocker _(fLock);
 
		context->fNextPrivateSemID = fNextPrivateSemID;
 
		// clone all semaphores
		SemTable::Iterator it = fSemaphores.GetIterator();
		while (TeamSemInfo* sem = it.Next()) {
			TeamSemInfo* clonedSem = sem->Clone();
			if (clonedSem == NULL)
				return NULL;
 
			if (context->fSemaphores.Insert(clonedSem) != B_OK) {
				delete clonedSem;
				return NULL;
			}
			context->fSemaphoreCount++;
		}
 
		contextDeleter.Detach();
		return context;
	}
 
	status_t OpenSem(const char* name, int openFlags, mode_t mode,
		uint32 semCount, sem_t* userSem, sem_t*& _usedUserSem, int32_t& _id,
		bool& _created)
	{
		NamedSem* sem = NULL;
		status_t error = sSemTable.OpenNamedSem(name, openFlags, mode, semCount,
			sem, _created);
		if (error != B_OK)
			return error;
 
		MutexLocker _(fLock);
 
		TeamSemInfo* teamSem = fSemaphores.Lookup(sem->ID());
		if (teamSem != NULL) {
			// already open -- just increment the open count
			teamSem->Open();
			sem->ReleaseReference();
			_usedUserSem = teamSem->UserSemaphore();
			_id = teamSem->ID();
			return B_OK;
		}
 
		// not open yet -- create a new team sem
 
		// first check the semaphore limit, though
		if (fSemaphoreCount >= MAX_POSIX_SEMS_PER_TEAM) {
			sem->ReleaseReference();
			if (_created)
				sSemTable.UnlinkNamedSem(name);
			return ENOSPC;
		}
 
		teamSem = new(std::nothrow) TeamSemInfo(sem, userSem);
		if (teamSem == NULL) {
			sem->ReleaseReference();
			if (_created)
				sSemTable.UnlinkNamedSem(name);
			return B_NO_MEMORY;
		}
 
		error = fSemaphores.Insert(teamSem);
		if (error != B_OK) {
			delete teamSem;
			if (_created)
				sSemTable.UnlinkNamedSem(name);
			return error;
		}
 
		fSemaphoreCount++;
 
		_usedUserSem = teamSem->UserSemaphore();
		_id = teamSem->ID();
 
		return B_OK;
	}
 
	status_t CloseSem(sem_id id, sem_t*& deleteUserSem)
	{
		deleteUserSem = NULL;
 
		MutexLocker _(fLock);
 
		TeamSemInfo* sem = fSemaphores.Lookup(id);
		if (sem == NULL)
			return B_BAD_VALUE;
 
		if (sem->Close()) {
			// last reference closed
			fSemaphores.Remove(sem);
			fSemaphoreCount--;
			deleteUserSem = sem->UserSemaphore();
			delete sem;
		}
 
		return B_OK;
	}
 
	status_t AcquireSem(sem_id id, bigtime_t timeout)
	{
		MutexLocker locker(fLock);
 
		TeamSemInfo* sem = fSemaphores.Lookup(id);
		if (sem == NULL)
			return B_BAD_VALUE;
		else
			id = sem->SemaphoreID();
 
		locker.Unlock();
 
		status_t error;
		if (timeout == 0) {
			error = acquire_sem_etc(id, 1, B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT,
				0);
		} else if (timeout == B_INFINITE_TIMEOUT) {
			error = acquire_sem_etc(id, 1, B_CAN_INTERRUPT, 0);
		} else {
			error = acquire_sem_etc(id, 1,
				B_CAN_INTERRUPT | B_ABSOLUTE_REAL_TIME_TIMEOUT, timeout);
		}
 
		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
	}
 
	status_t ReleaseSem(sem_id id)
	{
		MutexLocker locker(fLock);
 
		TeamSemInfo* sem = fSemaphores.Lookup(id);
		if (sem == NULL)
			return B_BAD_VALUE;
		else
			id = sem->SemaphoreID();
 
		locker.Unlock();
 
		status_t error = release_sem(id);
		return error == B_BAD_SEM_ID ? B_BAD_VALUE : error;
	}
 
	status_t GetSemCount(sem_id id, int& _count)
	{
		MutexLocker locker(fLock);
 
		TeamSemInfo* sem = fSemaphores.Lookup(id);
		if (sem == NULL)
				return B_BAD_VALUE;
		else
			id = sem->SemaphoreID();
 
		locker.Unlock();
 
		int32 count;
		status_t error = get_sem_count(id, &count);
		if (error != B_OK)
			return error;
 
		_count = count;
		return B_OK;
	}
 
private:
	sem_id _NextPrivateSemID()
	{
		while (true) {
			if (fNextPrivateSemID >= 0)
				fNextPrivateSemID = -1;
 
			sem_id id = fNextPrivateSemID--;
			if (fSemaphores.Lookup(id) == NULL)
				return id;
		}
	}
 
private:
	typedef BOpenHashTable<TeamSemHashDefinition, true> SemTable;
 
	mutex		fLock;
	SemTable	fSemaphores;
	int32		fSemaphoreCount;
	sem_id		fNextPrivateSemID;
};
 
 
// #pragma mark - implementation private
 
 
static realtime_sem_context*
get_current_team_context()
{
	Team* team = thread_get_current_thread()->team;
 
	// get context
	realtime_sem_context* context = atomic_pointer_get(
		&team->realtime_sem_context);
	if (context != NULL)
		return context;
 
	// no context yet -- create a new one
	context = new(std::nothrow) realtime_sem_context;
	if (context == NULL || context->Init() != B_OK) {
		delete context;
		return NULL;
	}
 
	// set the allocated context
	realtime_sem_context* oldContext = atomic_pointer_test_and_set(
		&team->realtime_sem_context, context, (realtime_sem_context*)NULL);
	if (oldContext == NULL)
		return context;
 
	// someone else was quicker
	delete context;
	return oldContext;
}
 
 
static status_t
copy_sem_name_to_kernel(const char* userName, KPath& buffer, char*& name)
{
	if (userName == NULL)
		return B_BAD_VALUE;
	if (!IS_USER_ADDRESS(userName))
		return B_BAD_ADDRESS;
 
	if (buffer.InitCheck() != B_OK)
		return B_NO_MEMORY;
 
	// copy userland path to kernel
	name = buffer.LockBuffer();
	ssize_t actualLength = user_strlcpy(name, userName, buffer.BufferSize());
 
	if (actualLength < 0)
		return B_BAD_ADDRESS;
	if ((size_t)actualLength >= buffer.BufferSize())
		return ENAMETOOLONG;
 
	return B_OK;
}
 
 
// #pragma mark - kernel internal
 
 
void
realtime_sem_init()
{
	new(&sSemTable) GlobalSemTable;
	if (sSemTable.Init() != B_OK)
		panic("realtime_sem_init() failed to init global sem table");
}
 
 
void
delete_realtime_sem_context(realtime_sem_context* context)
{
	delete context;
}
 
 
realtime_sem_context*
clone_realtime_sem_context(realtime_sem_context* context)
{
	if (context == NULL)
		return NULL;
 
	return context->Clone();
}
 
 
// #pragma mark - syscalls
 
 
status_t
_user_realtime_sem_open(const char* userName, int openFlagsOrShared,
	mode_t mode, uint32 semCount, sem_t* userSem, sem_t** _usedUserSem)
{
	realtime_sem_context* context = get_current_team_context();
	if (context == NULL)
		return B_NO_MEMORY;
 
	if (semCount > MAX_POSIX_SEM_VALUE)
		return B_BAD_VALUE;
 
	// userSem must always be given
	if (userSem == NULL)
		return B_BAD_VALUE;
	if (!IS_USER_ADDRESS(userSem))
		return B_BAD_ADDRESS;
 
	// check user pointers
	if (_usedUserSem == NULL)
		return B_BAD_VALUE;
	if (!IS_USER_ADDRESS(_usedUserSem) || !IS_USER_ADDRESS(userName))
		return B_BAD_ADDRESS;
 
	// copy name to kernel
	KPath nameBuffer(B_PATH_NAME_LENGTH);
	char* name;
	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
	if (error != B_OK)
		return error;
 
	// open the semaphore
	sem_t* usedUserSem;
	bool created = false;
	int32_t id;
	error = context->OpenSem(name, openFlagsOrShared, mode, semCount, userSem,
		usedUserSem, id, created);
	if (error != B_OK)
		return error;
 
	// copy results back to userland
	if (user_memcpy(&userSem->u.named_sem_id, &id, sizeof(int32_t)) != B_OK
		|| user_memcpy(_usedUserSem, &usedUserSem, sizeof(sem_t*)) != B_OK) {
		if (created)
			sSemTable.UnlinkNamedSem(name);
		sem_t* dummy;
		context->CloseSem(id, dummy);
		return B_BAD_ADDRESS;
	}
 
	return B_OK;
}
 
 
status_t
_user_realtime_sem_close(sem_id semID, sem_t** _deleteUserSem)
{
	if (_deleteUserSem != NULL && !IS_USER_ADDRESS(_deleteUserSem))
		return B_BAD_ADDRESS;
 
	realtime_sem_context* context = get_current_team_context();
	if (context == NULL)
		return B_BAD_VALUE;
 
	// close sem
	sem_t* deleteUserSem;
	status_t error = context->CloseSem(semID, deleteUserSem);
	if (error != B_OK)
		return error;
 
	// copy back result to userland
	if (_deleteUserSem != NULL
		&& user_memcpy(_deleteUserSem, &deleteUserSem, sizeof(sem_t*))
			!= B_OK) {
		return B_BAD_ADDRESS;
	}
 
	return B_OK;
}
 
 
status_t
_user_realtime_sem_unlink(const char* userName)
{
	// copy name to kernel
	KPath nameBuffer(B_PATH_NAME_LENGTH);
	char* name;
	status_t error = copy_sem_name_to_kernel(userName, nameBuffer, name);
	if (error != B_OK)
		return error;
 
	return sSemTable.UnlinkNamedSem(name);
}
 
 
status_t
_user_realtime_sem_get_value(sem_id semID, int* _value)
{
	if (_value == NULL)
		return B_BAD_VALUE;
	if (!IS_USER_ADDRESS(_value))
		return B_BAD_ADDRESS;
 
	realtime_sem_context* context = get_current_team_context();
	if (context == NULL)
		return B_BAD_VALUE;
 
	// get sem count
	int count;
	status_t error = context->GetSemCount(semID, count);
	if (error != B_OK)
		return error;
 
	// copy back result to userland
	if (user_memcpy(_value, &count, sizeof(int)) != B_OK)
		return B_BAD_ADDRESS;
 
	return B_OK;
}
 
 
status_t
_user_realtime_sem_post(sem_id semID)
{
	realtime_sem_context* context = get_current_team_context();
	if (context == NULL)
		return B_BAD_VALUE;
 
	return context->ReleaseSem(semID);
}
 
 
status_t
_user_realtime_sem_wait(sem_id semID, bigtime_t timeout)
{
	realtime_sem_context* context = get_current_team_context();
	if (context == NULL)
		return B_BAD_VALUE;
 
	return syscall_restart_handle_post(context->AcquireSem(semID, timeout));
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashLink.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fNextPrivateSemID.