Code:
#include "rawpaw.h"
static pawjd owners_perm = PAWIO_O_RW_ACCESS | PAWIO_O_EX_ACCESS;
static pawjd public_perm = PAWIO_O_PUBLIC_RW | PAWIO_O_PUBLIC_EX;
static pawjd groups_perm = PAWIO_O_GROUPS_RW | PAWIO_O_GROUPS_EX;
#ifdef PAW_API_MSW
typedef CRITICAL_SECTION gate_t;
static void open_gate( gate_t *gate ) { LeaveCriticalSection(gate); }
static void lock_gate( gate_t *gate ) { EnterCriticalSection(gate); }
static void term_gate( gate_t *gate ) { DeleteCriticalSection(gate); }
static pawd init_gate( gate_t *gate )
{
SetLastError(0);
InitializeCriticalSection(gate);
return GetLastError() ? ENOMEM : 0;
}
#else
typedef sem_t gate_t;
static void open_gate( gate_t *gate ) { sem_post( gate ); }
static void lock_gate( gate_t *gate ) { sem_wait( gate ); }
static void term_gate( gate_t *gate ) { sem_destroy( gate ); }
static pawd init_gate( gate_t *gate ) { return sem_init( gate, 0, 1 ); }
#endif
typedef struct _mutex_t mutex_t;
struct _mutex_t
{
void *ud;
pawcls *cls;
gate_t *gate;
paws name;
pawd threadc, allowed;
pawref lock, owner, *threads;
};
/* Default callback & class */
static void void_class_delCB( void *ud ) { (void)ud; }
static pawcls void_class = { "void", NULL, void_class_delCB };
/* Mutex functions */
RAWPAW_API paws pawmtx_getname( pawmtx mtx ) { return ((mutex_t*)mtx)->name; }
RAWPAW_API PAWCLS pawmtx_getclass( pawmtx mtx ) { return ((mutex_t*)mtx)->cls; }
RAWPAW_API pawref pawmtx_lockedby( pawmtx mtx ) { return ((mutex_t*)mtx)->lock; }
RAWPAW_API pawjd pawmtx_getowner( pawmtx mtx ) { return ((mutex_t*)mtx)->owner.tid; }
/* Assumes gate is locked & add is greater than 0 */
static pawref* more_refs( mutex_t *mutex, pawd add )
{
pawd i = mutex->threadc;
pawref *refs = pawget( mutex->threads, sizeof(pawref) * (i + add) );
/* Presumably no memory but never know */
if ( !refs )
return NULL;
mutex->threadc += add;
mutex->threads = refs;
pawset( refs + i, 0, sizeof(pawref) * add );
for ( ; i < add; ++i )
refs[i].tid = -1;
return refs;
}
RAWPAW_API pawmtx pawmtx_create( paws name, void *ud, PAWCLS cls, pawd threads )
{
pawd err = 0;
gate_t *gate = NULL;
mutex_t *mutex = NULL;
cls = cls ? cls : &void_class;
/* Clear a negative value */
threads *= (threads >= 0);
/* Ensure at least 1 slot is allocated for the current thread to be
* pre-attached */
threads += (thread < 1);
if ( !(cls->type && cls->cb) )
{
errno = EINVAL;
return NULL;
}
gate = pawgetUD( sizeof(gate_t) + sizeof(mutex_t) );
if ( !gate )
return NULL;
mutex = (mutex_t*)(gate + 1);
mutex->gate = gate;
if ( !more_refs( mutex, threads ) )
{
pawdel(gate);
return NULL;
}
err = init_gate( &(mutex->gate) );
if ( err )
{
pawdel(mutex->threads);
pawdel(gate);
return NULL;
}
/* Assign this thread as the owner */
mutex->owner.tid = tid;
/* Declare what type of threads are permitted to attach themselves */
mutex->owner.perm |= 0777;
/* Declare the number of threads allowed to be just 1, this thread */
mutex->allowed = 1;
/* Pre-lock the mutex */
mutex->lock.ref = 1;
mutex->lock.tid = pawgetid();
/* Attach this thread to the mutex */
mutex->owner.refc = 1;
mutex->threads->ref = 1;
mutex->threads->tid = mutex->lock.tid;
mutex->threads->perm = mutex->owner.perm;
/* Ensure this points to a valid string even if the string is empty */
mutex->name = "";
/* Assign the pointer & data handed to us, do nothing else with it
* until it's time to delete the mutex */
mutex->cls = cls;
mutex->ud = ud;
mutex->gc = gc;
return mutex;
}
RAWPAW_API void* pawmtx_attach( pawmtx *mtx, bool withlock, pawjd wait )
{
pawjd tics = 0;
mutex_t *mutex = *mtx;
pawjd tid = pawgetid();
clock_t prv = 0, now = 0;
gate_t *gate = NULL;
pawref *ref = NULL, refs = NULL;
pawd i = 0, have = 0, timed = (wait >= 0), timeout = 0, gperm = 0, pperm = 0;
if ( !mutex || !(mutex->valid) )
{
pawsig( PAWSIG_SEGV );
return NULL;
}
gate = mutex->gate
lock_gate( gate );
/* Check if the current thread has permission to attach itself to the
* mutex */
have = mutex->threadc;
refs = mutex->threads;
for ( ; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
{
gperm = ref->perm & mutex.owner.perm & groups_perm;
pperm = ref->perm & mutex.owner.perm & public_perm;
if ( gperm != groups_perm && pperm != public_perm )
{
/* Thread permission was revoked, cannot attach until regain
* the permission */
open_gate(gate);
return NULL;
}
break;
}
}
if ( i == have )
{
/* Do we have permission despite not being part of the group? */
if ( (mutex->owner.perm & public_perm) != public_perm )
{
open_gate(gate);
return NULL;
}
/* Let's se if we can add the thread to the list of known threads */
refs = more_refs( mutex, have );
if ( !refs )
{
open_gate(gate);
return NULL;
}
ref = refs + i;
ref->tid = tid;
ref->ref = 1;
/* Declare that the thread is not part of the group yet */
ref->perm = public_perm;
}
/* No point looping if don't need to */
if ( withlock && mutex->valid )
{
if ( mutex->tid == tid )
{
withlock = false;
mutex->locks++;
}
else if ( mutex->tid < 0 && mutex->valid )
{
withlock = false;
mutex->lock.tid = tid;
mutex->lock.ref = 1;
}
}
ref->ref++;
/* Total references */
mutex->owner.refc += mutex->valid;
open_gate( gate );
if ( !(mutex->allowed) || !withlock || wait == 0 )
return mutex->allowed ? mutex->ud : NULL;
while ( 1 )
{
lock_gate( gate );
/* The pointer may have changed since we released the actual lock */
ref = mutex->threads + i;
gperm = ref->perm & mutex.owner.perm & groups_perm;
pperm = ref->perm & mutex.owner.perm & public_perm;
if ( gperm != groups_perm && pperm != public_perm )
{
/* Permission to attach was revoked while we were waiting, undo
* our increment to the counts */
ref->ref--;
mutex->owner.ref--;
open_gate(gate);
return NULL;
}
if ( mutex->tid < 0 )
{
mutex->lock.tid = tid;
mutex->lock.ref = 1;
open_gate(gate);
return mutex->ud;
}
now = clock();
tics = now - prv;
timeout = (tics >= wait);
if ( timed * timeout )
{
/* Remove our increment before we fail */
ref->ref--;
mutex->owner.refc--;
open_gate(gate);
errno = ETIMEDOUT;
return NULL;
}
open_gate(gate);
}
errno = -1;
return NULL;
}
RAWPAW_API pawd pawmtx_detach( pawmtx *mtx, bool tooklock )
{
pawd i = 0, have = 0;
mutex_t *mutex = *mtx;
pawjd tid = pawgetid();
pawref *refs = NULL, *ref = NULL;
gate_t *gate = &(mutex->gc->gate);
/* Decrement the lock count if the thread had it */
if ( mutex->lock.tid == tid )
{
mutex->lock.ref--;
/* Release the lock now that the count hit 0 */
if ( mutex->lock.ref < 1 )
mutex->lock.tid = -1;
}
lock_gate( gate );
have = mutex->threadc;
refs = mutex->threads;
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
break;
}
if ( i == have || ref->ref < 1 )
{
/* Thread was never attached, dev did something wrong */
open_gate(gate);
return EBADR;
}
ref->ref--;
mutex->owner.refc--;
open_gate( gate );
return 0;
}
RAWPAW_API pawd pawmtx_revoke( pawmtx mtx, pawjd tid )
{
pawd i = 0, have = 0;
mutex_t *mutex = mtx;
pawjd id = pawgetid();
gate_t *gate = mutex->gate;
pawref *refs = NULL, *ref = NULL;
lock_gate(gate);
have = mutex->threadc;
refs = mutex->threads;
if ( mutex->owner.tid != id )
{
/* Only the owner can remove public permissions */
if ( tid < 0 )
{
open_gate(gate);
return EPERM;
}
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
{
ref->perm &= ~groups_perm;
open_gate(gate);
return 0;
}
}
/* Only the owner can remove group permissions from other threads */
open_gate(gate);
return EPERM;
}
if ( tid < 0 )
{
mutex->owner.perm &= ~public_perm;
open_gate(gate);
return 0;
}
else if ( tid == id )
{
pawd bound = 0, allowed = 0;
for ( i = 0; i < have; ++i )
{
ref = refs + i;
bound += (ref->ref > 0);
allowed += (ref->perm != 0);
}
if ( bound || allowed > 1 )
{
/* Indicate permission could not be removed because mutex is in use
* and doing so would trigger deletion which would crash the app */
open_gate(gate);
return EADDRINUSE;
}
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
{
ref->perm &= ~groups_perm;
open_gate(gate);
return 0;
}
}
}
else
{
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
{
ref->perm &= ~groups_perm;
open_gate(gate);
return 0;
}
}
/* Thread never got permission to begin with so no removal needed */
open_gate(gate);
return 0;
}
mutex->owner.tid = -1;
mutex->owner.perm = 0;
/* Since only the owning thread can get here and only after all
* permissions and attachments have been removed it is safe to say the lock
* is now unused, we set it just in case there's a bug somewhere that
* allows an unauthorised lock */
mutex->lock.tid = tid;
mutex->lock,ref = 1;
/* We don't want a deadlock happening during the callback so we just let
* this go in case it does try something stupid */
open_gate( gate );
/* Now it's safe to release any pointers */
mutex->cls->cb( mutex->ud );
term_gate( mutex->gate );
pawdel( mutex->gate );
return 0;
}
RAWPAW_API pawd pawmtx_permit( pawmtx mtx, pawjd tid )
{
pawd i = 0, have = 0;
mutex_t *mutex = mtx;
pawjd id = pawgetid();
gate_t *gate = mutex->gate;
pawref *refs = NULL, *ref = NULL;
lock_gate(gate);
/* We lock the gate for this check because it is possible that the owner
* will pass ownership to this thread between our check and rejecting the
* attempt, locking the gate 1st ensure the change of ownership occurs 1st */
if ( mutex->owner.tid != id )
{
/* Only the owner can add group permissions to threads */
open_gate(gate);
return EPERM;
}
have = mutex->threadc;
refs = mutex->threads;
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
{
/* Doesn't matter if the owner is giving itself group permissions,
* it changes nothing as it already had them */
ref->perm |= 077;
open_gate(gate);
return 0;
}
}
refs = more_refs( mutex, have );
if ( !refs )
{
open_gate(gate);
return errno;
}
ref = refs + i;
ref->tid = tid;
ref->perm = 077;
open_gate(gate);
return 0;
}
RAWPAW_API pawd pawmkx_passto( pawmtx mtx, pawjd tid )
{
pawd i = 0, have = 0;
mutex_t *mutex = mtx;
pawjd id = pawgetid();
gate_t *gate = mutex->gate;
pawref *refs = NULL, *ref = NULL;
lock_gate(gate);
if ( mutex->owner.tid != id )
{
open_gate(gate);
return EPERM;
}
if ( tid == id )
{
/* Why would the owner even trigger this jump? */
open_gate(gate);
return 0;
}
have = mutex->threadc;
refs = mutex->threads;
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == tid )
goto shift_perm;
}
refs = more_refs( mutex, have );
if ( !refs )
{
open_gate(gate);
return errno;
}
ref = refs + i;
ref->tid = tid;
shift_perm:
ref->perm = 0777;
mutex->owner.tid = tid;
for ( i = 0; i < have; ++i )
{
ref = refs + i;
if ( ref->tid == id )
{
ref->perm &= ~0700;
open_gate(gate);
return 0;
}
}
/* Bug if this occurs, indicate as such */
open_gate(gate);
return EBADR;
}