/* **********************************************************************
 * *	                                                                *
 * *           Routines for Semaphores and Event Flags                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/semaphore.c
 *
 *         COPYRIGHT (c) 1994, 1996, 1998, 1999, 2002, 2004 BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 *
 * Common Routines:
 *   create_sem_set()     Create a named set of semaphores / event flags
 *   init_sem_set()       Initialize the created set
 *   kill_sem_set()       Destroy a set
 *
 * Semaphore group:
 *   lock_sem()           Lock a semaphore (PEE)
 *   unlock_sem()         Unlock a semaphore (VEE)
 *   read_sem()           Return current state of a semaphore
 *   locked_by_me()       Have we locked a given semaphore ?
 *
 * Event flag group:
 *   sig_waitef()         Wait for event flag to be set
 *   sig_setef()          Set event flag
 *   sig_clref()          Clear event flag
 *   sig_readef()         Is a given event flag set ?
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/stat.h>

#include "sigtypes.h"
#include "event_flags.h"
#include "user_lib.h"

/* This structure is sometimes defined in sys/sem.h, but not always.
 * It is used by the semctl function.
 */
#ifndef IRIX
union semun
{
  int val;			/* value for SETVAL */
  struct semid_ds *buf;		/* buffer for IPC_STAT & IPC_SET */
  unsigned short int *array;	/* array for GETALL & SETALL */
  struct seminfo *__buf;	/* buffer for IPC_INFO */
};
#endif  /* #ifndef IRIX */

/* The following local storage keeps track of the semaphore usage
 * by this particular process.
 */
struct local_cluster {
  UINT4 isem_count ;		           /* Number of semaphores allocated */
  UINT4 iflg_count ;		           /* Number of eventflags allocated */
  UINT2 msemlocks[MAX_SEM_CLUSTER_SIZE] ;  /* List of locks private to this process */
  UINT4 highestlock ;		           /* Highest private semaphore lock */
  SINT4 semid ;			           /* Unique identifier */
  key_t iKey;			           /* Unique key value */
} ;

static struct local_cluster clusters_c[MAX_SEM_CLUSTERS] = {{0},} ;

#define CLUSTER(IVAL) (((IVAL) >> 16) & 0xFF)
#define  SEMVAL(IVAL) ((IVAL) & 0xFFFF)

/* ======================================================================
 * Create/MapTo a named set of shared semaphores/eventflags.  Errors all signaled
 * internally.
 *
 * Entry Parameters
 *         SNAME_A   Null-terminated unique name for the area.  On UNIX
 *                   platforms there must exist a file: IRIS_KEYS//SNAME_A
 *                   in order for the shared section to be created.
 *     ISEM_COUNT_A  Number of semaphores to allocate.
 *     IFLG_COUNT_A  Number of eventflags to allocate.
 *   ISEM_CLUSTER_A  Cluster number parameter, allows multiple sets of
 *                   semaphores to be active at once within a given process.
 *
 * Exit Parameters
 *   Return Status
 */
MESSAGE create_sem_set
( const char* sname_a,
  UINT4 isem_count_a,
  UINT4 iflg_count_a,
  UINT4 isem_cluster_a )
{
  MESSAGE istatus ; UINT4 icluster = CLUSTER(isem_cluster_a) ;
  key_t iKey ;
  struct local_cluster *clstr;
  SINT4 semid;

  /* Verify that the cluster is valid and free.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) 
    {
      sig_signal_va( USER_CLUSTER_BAD, 2, icluster, sname_a );
      return USER_ERROR_SIG;
    }

  istatus = sig_ftok( sname_a, (SINT4 *)&iKey ) ;
  if (istatus!=SS_NORMAL) return istatus;  /* sig_ftok signals internally */

  clstr = &clusters_c[icluster] ;
  if (clstr->iKey) 
    {
      if ((clstr->isem_count!=isem_count_a)||(clstr->iflg_count!=iflg_count_a)||
	  (clstr->iKey!=iKey))
	{
	  sig_signal_va( USER_CLUSTER_CHANGE, 1, sname_a );
	  return USER_ERROR_SIG;
	}
    }
    
  /* Allocate a set of system semaphores for both the
   * semaphore group and the event flag group.  The
   * semaphores come first; therefore the first semaphore
   * number is zero, and the first event flag number is
   * ISEM_COUNT_A.
   */
  semid = semget( iKey, (isem_count_a + iflg_count_a),
		  IPC_CREAT | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) ;
  
  if( semid == -1 ) {
    sig_signal( errno_to_message_va( errno, 2, "create_sem_set(%s)", sname_a ) ) ;
    sig_signal_va( USER_CLUSTER_ERROR, 1, sname_a );
    return USER_ERROR_SIG;     
  }

  clstr->isem_count = isem_count_a ;
  clstr->iflg_count = iflg_count_a ;
  memset( clstr->msemlocks, FALSE, sizeof(clstr->msemlocks) ) ;
  clstr->highestlock = 0 ;
  clstr->semid = semid ;
  clstr->iKey  = iKey;
  return( SS_NORMAL ) ;
}

/* ======================================================================
 * Check the specfied semaphore set to see if it is already mapped by this
 * process.  No errors are signalled, instead they just indicate FALSE.
 */
UINT2 lSemSetCheck
( UINT4 isem_cluster_a )  /* Cluster number parameter */
{
  UINT4 icluster = CLUSTER(isem_cluster_a) ;
  struct local_cluster *clstr;

  /* Verify that the cluster is valid.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) return FALSE;

  /* Check if it is free */
  clstr = &clusters_c[icluster] ;
  if( (clstr->isem_count != 0) || (clstr->iflg_count != 0) ) return TRUE;
  
  return FALSE;
}

/* ======================================================================
 * Initialize the set of shared semaphores.  This should be called after
 * calling creat_sem_set() by only one of the routines (the first one)
 * that is mapping to the shared flags.
 *
 * Exit Parameters
 *   Return Status
 */
MESSAGE init_sem_set( UINT4 isem_cluster_a ) 
{
  SINT4 ii ; MESSAGE istatus = SS_NORMAL ;
  UINT4 icluster = CLUSTER(isem_cluster_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    istatus = USER_CLUSTER_BAD ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      istatus = USER_CLUSTER_UNDEF ;
    } else {
      union semun Value;
      memset( &Value, 0, sizeof(Value) );

      /* Initialize the semaphores to "set" and the event flags
       * to "clr".
       */
      for( ii=0 ; ii < clstr->isem_count ; ii++ ) 
	{
	  Value.val = 1;
	  if( -1 == semctl( clstr->semid, ii, SETVAL, Value ) ) {
	    istatus = errno_to_message_va
	      ( errno, 3, "init_sem_set(%d) #%d", isem_cluster_a, ii ) ;
	    sig_signal( istatus ) ;
	  }
      }

      for( ii=0 ; ii < clstr->iflg_count ; ii++ ) {
	Value.val = 0;
	if( -1 == semctl( clstr->semid, (ii + clstr->isem_count), SETVAL, Value ) ) {
	  istatus = errno_to_message_va
	    ( errno, 3, "init_sem_set(%d) #%d", isem_cluster_a, ii ) ;
	  sig_signal( istatus ) ;
	}
      }
    }
  }
  return( istatus ) ;
}

/* ======================================================================
 * Kill a set of semaphores / event flags.
 */
MESSAGE kill_sem_set( UINT4 isem_cluster_a )
{
  MESSAGE istatus ;
  UINT4 icluster = CLUSTER(isem_cluster_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    istatus = USER_CLUSTER_BAD ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      istatus = USER_CLUSTER_UNDEF ;
    } else {
      union semun Value;
      memset( &Value, 0, sizeof(Value) );

      if( -1 == semctl( clstr->semid, 0, IPC_RMID, Value ) ) {
	istatus = errno_to_message_va
	  ( errno, 2, "kill_sem_set(%d)", isem_cluster_a ) ;
      } else {
	istatus = SS_NORMAL ;
      }

      clstr->isem_count = 0 ;
      clstr->iflg_count = 0 ;
    }
  }
  return( istatus ) ;
}

/* ======================================================================
 * Lock a semaphore
 *
 * Entry Parameters
 *   NUM_A      Semaphore number to lock
 */
void lock_sem( UINT4 num_a ) {
  UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {

      /* Check that the value is okay, that we have not locked this
       * one already, and that the locking order is okay.
       */
      UINT4 numz = SEMVAL(num_a) ;

      if( numz >= clstr->isem_count ) {
	sig_signal_va( USER_BADSEM, 1, num_a ) ; return ;
      }
      if( clstr->msemlocks[numz] ) {
	sig_signal_va( USER_RELOCK, 1, num_a ) ; return ;
      }
      if( numz < clstr->highestlock ) {
	sig_signal_va( USER_LOCK_ORDER, 2, numz, clstr->highestlock ) ; return ;
      }
      {
	struct sembuf sops[1] ;
	sops[0].sem_num = numz ;
	sops[0].sem_op  = -1   ;
	sops[0].sem_flg =  0   ;

	while( -1 == semop( clstr->semid, sops, 1 ) ) {
	  if( errno != EINTR ) {
	    sig_signal( errno_to_message_va( errno, 2, "lock_sem(%d)", num_a ) ) ;
	    break ;
	  }
	}
      }
      clstr->msemlocks[numz] = TRUE ;
      clstr->highestlock = MAX( clstr->highestlock, numz ) ;
    }
  }
}

/* ======================================================================
 * UnLock a semaphore
 *
 * Entry Parameters
 *   NUM_A      Semaphore number to unlock
 */
void unlock_sem( UINT4 num_a ) {
  SINT4 ii ; UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {

      /* Check that the value is okay, and that we have locked
       * this one already.
       */
      UINT4 numz = SEMVAL(num_a) ;

      if( numz >= clstr->isem_count ) {
	sig_signal_va( USER_BADSEM, 1, num_a ) ; return ;
      }
      if( ! clstr->msemlocks[numz] ) {
	sig_signal_va( USER_REUNLOCK, 1, num_a ) ; return ;
      }
      {
	struct sembuf sops[1] ;
	sops[0].sem_num = numz ;
	sops[0].sem_op  =  1   ;
	sops[0].sem_flg =  0   ;

	while( -1 == semop( clstr->semid, sops, 1 ) ) {
	  if( errno != EINTR ) {
	    sig_signal( errno_to_message_va( errno, 2, "unlock_sem(%d)", num_a ) ) ;
	    break ;
	  }
	}
      }

      /* Flag unlocked, and compute new highest lock
       */
      clstr->msemlocks[numz] = FALSE ;
      for( ii = clstr->highestlock ; ii > 0 ; ii-- )
	if( clstr->msemlocks[ ii ] ) break ;
      clstr->highestlock = ii ;
    }
  }
}

/* ======================================================================
 * Function returning NZ if and only if the indicated semaphore is presently
 * locked by any process.  This information can be useful for debugging.
 *
 * Entry Parameters
 *   NUM_A      Semaphore number to test
 * Exit Parameters
 *   IPID_A     ID of the last process to successfully lock the semaphore,
 *              or zero if the ID can not be determined.  Note that this
 *              is declared as UINT4 rather than pid_t, since not all
 *              platforms define the later.
 */
UINT2 read_sem( UINT4 num_a, UINT4 *ipid_a ) {
  SINT4 ival=0 ; UINT4 ipid=0 ; UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      /* Check that the semaphore is valid, and then attempt to
       * read its contents from the system.
       */
      if( numz >= clstr->isem_count ) {
	sig_signal_va( USER_BADSEM, 1, num_a ) ;
      } else {
	union semun Value;
	memset( &Value, 0, sizeof(Value) );
      
	ival = semctl( clstr->semid, numz, GETVAL, Value ) ;
	if( -1 == ival ) {
	  sig_signal( errno_to_message_va( errno, 2, "read_sem(%d)", num_a ) ) ;
	  ival=0 ;
	} else {
	  ival = (ival == 0) ;
	}

	ipid = semctl( clstr->semid, numz, GETPID, Value ) ;
	if( -1 == ipid ) {
	  sig_signal( errno_to_message_va( errno, 1, "read_sem(%d)", num_a ) ) ;
	  ipid=0 ;
	}
      }
    }
  }
  *ipid_a = ipid ; return( ival != 0 ) ;
}

/* ======================================================================
 * Function returning NZ if and only if the indicated semaphore has been
 * locked by this process.  This information can be useful when AST routines
 * need to do locks.
 *
 * Entry Parameters
 *   NUM_A      Semaphore number to test
 */
UINT2 locked_by_me( UINT4 num_a ) {
  UINT4 icluster = CLUSTER(num_a) ; UINT2 locked = FALSE ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      if( numz >= clstr->isem_count ) {
	sig_signal_va( USER_BADSEM, 1, num_a ) ; return 0 ;
      } else {
	locked = clstr->msemlocks[numz] ;
      }
    }
  }
  return( locked ) ;
}

/* ======================================================================
 * Wait for an event flag to be set
 *
 * Entry Parameters
 *   NUM_A      Event flag number to wait for
 */
void sig_waitef( UINT4 num_a ) {
  UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      /* Check for valid event flag value.
       */
      if( numz >= clstr->iflg_count ) {
	sig_signal_va( USER_BADEF, 1, num_a ) ; return ;
      }
      {
	struct sembuf sops[2] ; SINT4 ival ;
	union semun Value;
	memset( &Value, 0, sizeof(Value) );

	/* Get the current state of the event flag in order to
	 * verify that there are not other processes presently
	 * waiting on it.  This check is performed only for the
	 * purpose of signaling a warning, and does not have to
	 * be done atomicly with the actual wait operation.  The
	 * assumption that only one processes ever waits on an
	 * event flag was added in Oct 1994.
	 */
	ival = semctl( clstr->semid, (numz + clstr->isem_count), GETNCNT, Value ) ;
	if( ival == -1 )
	  sig_signal( errno_to_message_va( errno, 2, "sig_waitef(%d)", num_a ) ) ;
	else
	  if( ival !=  0 ) sig_signal_va( USER_WAITEF, 1, num_a ) ;

	sops[0].sem_num = (numz + clstr->isem_count) ;
	sops[0].sem_op  = -1   ;
	sops[0].sem_flg =  0   ;
	sops[1].sem_num = (numz + clstr->isem_count) ;
	sops[1].sem_op  =  1   ;
	sops[1].sem_flg =  0   ;

	while( -1 == semop( clstr->semid, sops, 2 ) ) {
	  if( errno != EINTR ) {
	    sig_signal( errno_to_message_va( errno, 2, "sig_waitef(%d)", num_a ) ) ;
	    break ;
	  }
	}
      }
    }
  }
}

/* ======================================================================
 * Set an event flag.  Wake up any processes that are currently blocked
 * on the flag.  Returns the previous value of the flag.
 *
 * Entry Parameters
 *   NUM_A      Event flag number to set
 */
UINT2 sig_setef( UINT4 num_a ) {
  SINT4 value = 0 ; UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      /* Check for valid event flag value.
       */
      if( numz >= clstr->iflg_count ) {
	sig_signal_va( USER_BADEF, 1, num_a ) ;

      } else {
	union semun Value;
	memset( &Value, 0, sizeof(Value) );
	value = semctl( clstr->semid, (numz + clstr->isem_count), GETVAL, Value ) ;
	if( value == -1 ) {
	  sig_signal( errno_to_message_va( errno, 2, "sig_setef(%d)", num_a ) ) ;
	  value = 0 ;
	} else {
	  Value.val = 1;
	  if( -1 == semctl( clstr->semid, (numz + clstr->isem_count), SETVAL, Value ) )
	    {
	      sig_signal( errno_to_message_va( errno, 2, "sig_setef(%d)", num_a ) ) ;
	      value = 0 ;
	    }
	}
      }
    }
  }
  return( value ) ;
}

/* ======================================================================
 * Clear an event flag.  Returns the previous value of the flag.
 *
 * Entry Parameters
 *   NUM_A      Event flag number to clear
 */
UINT2 sig_clref( UINT4 num_a ) {
  SINT4 value = 0 ; UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      /* Check for valid event flag value.
       */
      if( numz >= clstr->iflg_count ) {
	sig_signal_va( USER_BADEF, 1, num_a ) ;

      } else {
	union semun Value;
	memset( &Value, 0, sizeof(Value) );
	value = semctl( clstr->semid, (numz + clstr->isem_count), GETVAL, Value ) ;
	if( value == -1 ) {
	  sig_signal( errno_to_message_va( errno, 2, "sig_clref(%d)", num_a ) ) ;
	  value=0 ;
	} else {
	  Value.val = 0;
	  if( -1 == semctl( clstr->semid, (numz + clstr->isem_count), SETVAL, Value ) ) {
	    sig_signal( errno_to_message_va( errno, 2, "sig_clref(%d)", num_a ) ) ;
	    value=0 ;
	  }
	}
      }
    }
  }
  return( value ) ;
}

/* ======================================================================
 * Return present state of an event flag.
 *
 * Entry Parameters
 *   NUM_A      Event flag number to clear
 */
UINT2 sig_readef( UINT4 num_a ) {
  SINT4 value = 0 ; UINT4 icluster = CLUSTER(num_a) ;

  /* Verify that the cluster is valid and defined.
   */
  if( icluster >= MAX_SEM_CLUSTERS ) {
    sig_signal_va( USER_CLUSTER_BAD, 1, num_a ) ;
  } else {
    struct local_cluster *clstr = &clusters_c[icluster] ;
    if( (clstr->isem_count == 0) && (clstr->iflg_count == 0) ) {
      sig_signal_va( USER_CLUSTER_UNDEF, 1, num_a ) ;
    } else {
      UINT4 numz = SEMVAL(num_a) ;

      /* Check for valid event flag value.
       */
      if( numz >= clstr->iflg_count ) {
	sig_signal_va( USER_BADEF, 1, num_a ) ;

      } else {
	union semun Value;
	memset( &Value, 0, sizeof(Value) );
	value = semctl( clstr->semid, (numz + clstr->isem_count), GETVAL, Value ) ;
	if( value == -1 ) {
	  sig_signal( errno_to_message_va( errno, 2, "sig_readef(%d)", num_a ) ) ;
	  value=0 ;
	}
      }
    }
  }
  return( value ) ;
}
