/*
 * Provide support routines for managing index file used for counting
 * accesses:
 *
 *   void *ifopen ( char *fname, char *mode );
 *   int ifclose ( void *ifile );
 *   int iferror ( void *ifile );
 *   int ifread_rec ( char *buf, size_t bufsize, size_t *len, void *ifile,
 *		char *key, int key_size );
 *   int ifwrite_rec ( char *buf, size_t bufsize, void *ifile );
 *   int ifupdate_rec ( char *buf, size_t bufsize, void *ifile );
 *   int ifdlcreate ( char *fld_str, char *fname, char *defname );
 *   int icheck_access ( char *fname, int uic );
 *
 * Base on indexio.c originally supplied by Ken Covert.
 *
 * Revised: 23-APR-1995		Added ifdlcreate and icheck_access. (DLJ)
 * Revised: 24-APR-1995		Add defaul ACL if empty.
 * Revised: 25-APR-1995		Cleanup up RMS calls, delete unused routine.
 * Revised: 18-JAN-1996		Added ifdelete_rec function.
 * Revised:  11-MAY1-996	Convert to multithreaded use.
 * Revised:  15-MAY-1997	fix improper pthread call (unlock) in icheck_access.
 */
#include "pthread_1c_np.h"
#include <stdio.h>
#include <rms.h>
#include <string.h>
#include <starlet.h>
#include <lib$routines.h>
#include <ssdef.h>
#include <acldef.h>
#include <armdef.h>
#include <chpdef.h>
#include <fdldef.h>
#include <jpidef.h>
#include <descrip.h>
#include <stdlib.h>
#include <stsdef.h>
typedef struct {
  struct FAB fab;
  struct RAB rab;
  union {
    int rc;
    struct {
      unsigned severity:1;
      unsigned return_condition:31;
    } status_parts;
  } status;
} IFILE;
#define IFILE_T 1
#include "access_db.h"
#include "tmemory.h"

/*****************************************************************************/
/* Since ifdlcreate and icheck_access are called extremely rarely, dynamically
 * load FDLSHR and SECURESHR so we don't burden every run of the program
 * with 3 additional shareable image activations.
 *
 * Revert to simple use.
 */
#define fdl_create FDL$CREATE
#define sys_change_acl sys$change_acl
extern int fdl_create(), sys_change_acl();
             
/****************************************************************************/
/* Define once routine for setting up synchronization objects.  Since we
 * can't pass an argument in the RMS AST, use global condition variable.
 * Serialize access to this variable with access_lock mutex.
 */
static pthread_once_t module_setup = PTHREAD_ONCE_INIT;
static pthread_mutex_t access_lock;	/* serializes access to synch rtn */
static pthread_mutex_t rms_io;
static pthread_cond_t rms_io_done;

static void synch_ast ( struct RAB *rab )
{
    pthread_cond_signal_int_np ( &rms_io_done );
}
static int synch_rms ( int status, unsigned int *sts )
{
    /*
     * Loop until predicate (iosb nozero) is true.
     */
    if ( (status == SS$_SYNCH) || (status == RMS$_SYNCH) ) {
	status = *sts;
    } else if ( ((status&1) == 1) || (status==0) ) {
	do {
	    pthread_cond_wait ( &rms_io_done, &rms_io );
	} while ( *sts == 0 );
	status = *sts;
    }
    pthread_mutex_unlock ( &rms_io );
    return status;
}

static void module_init ()
{
    INITIALIZE_MUTEX ( &access_lock );
    SET_MUTEX_NAME ( &access_lock,"OSU access DB lock" )
    INITIALIZE_MUTEX ( &rms_io );
    SET_MUTEX_NAME ( &rms_io, "OSU access DB RMS" )
    INITIALIZE_CONDITION ( &rms_io_done );
    SET_COND_NAME ( &rms_io_done,"OSU access DB RMS" )
}

int iacquire_db()
{
    pthread_once ( &module_setup, module_init );
    return pthread_mutex_lock ( &access_lock);
}

int irelease_db()
{
    pthread_mutex_unlock ( &access_lock );
    return 1;
}
/****************************************************************************/
int ifdlcreate ( char *fdl, char *fname, char *defname )
{
    int status, flags;
    $DESCRIPTOR(fdl_desc,"");
    $DESCRIPTOR(filename,"");
    $DESCRIPTOR(default_name,"");
    /*
     * Set descriptors to point to passed strings and call FDL$CREATE.
     */
    fdl_desc.dsc$w_length = strlen ( fdl );
    fdl_desc.dsc$a_pointer = fdl;
    filename.dsc$w_length = strlen ( fname );
    filename.dsc$a_pointer = fname;
    default_name.dsc$w_length = strlen(defname);
    default_name.dsc$a_pointer = defname;
    flags = FDL$M_FDL_STRING | FDL$M_SIGNAL;

    status = fdl_create (&fdl_desc, &filename, &default_name, 0, 0, &flags);
    return status;
}
/****************************************************************************/
/* Retrieve ACL on indicated filename and see if it permits read access
 * to specified UIC.
 */
int icheck_access ( char *fname, int uic )
{
    struct { short len, code; char *buffer; int *retlen; } itemlist[10];
#define SETITEM(itm,a,b,c,d) \
	itm.len=a; itm.code=b; itm.buffer=(char *) c; itm.retlen=(int *) d;
    int status, acllen,  context, objtype;
    long access, rights[2], privs[2];
    char *acl;
    $DESCRIPTOR(filename,"");
    /*
     * Get length of ACL.
     */
    pthread_once ( &module_setup, module_init );
    SETITEM(itemlist[0],4,ACL$C_ACLLENGTH, &acllen, 0)
    itemlist[1].len = itemlist[1].code = 0;

    objtype = ACL$C_FILE;
    context = 0;
    filename.dsc$w_length = strlen ( fname );
    filename.dsc$a_pointer = fname;

    status = sys_change_acl ( 0, &objtype, &filename, &itemlist,
		0, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Allocate buffer and read entire ACL.
     */
    acl = tm_malloc ( acllen );
    if ( !acl ) return 12;
    SETITEM(itemlist[0],acllen,ACL$C_READACL,acl,&acllen)

    status = sys_change_acl( 0, &objtype, &filename, &itemlist,
		0, 0, 0, 0, 0 );
    if ( status == SS$_ACLEMPTY ) {
	/*
	 * Make phony ACL granting access to current process UIC.
	 */
	int iosb[2], pid, proc_uic;
	SETITEM(itemlist[0],4,JPI$_UIC,&proc_uic, 0);
        itemlist[1].len = itemlist[1].code = 0;		/* null list */
	pid = 0;
	pthread_mutex_lock ( &rms_io );
	status = sys$getjpi ( 0, &pid, 0, &itemlist, iosb, synch_ast, 0 );
	status = synch_rms ( status, (unsigned int *) iosb );
	if ( (status&1) == 1 ) {
	    if ( proc_uic != uic ) status = SS$_NOPRIV;
	}
    }
    else if ( (status&1) == 1 ) {
	/*
	 * We have ACL, construct item list for $CHKPRO.
	 */
	access = ARM$M_READ;
	rights[0] = uic;
	rights[1] = 0;
	privs[0] = privs[1] = 0;
	SETITEM(itemlist[0],4,CHP$_ACCESS,&access,0)
	SETITEM(itemlist[1],8,CHP$_RIGHTS,rights,0)
	SETITEM(itemlist[2],8,CHP$_PRIV,privs,0)
	SETITEM(itemlist[3],acllen,CHP$_ACL,acl,0)
        itemlist[4].len = itemlist[4].code = 0;

	status = sys$chkpro ( &itemlist );
    }
    tm_free ( acl );
    return status;
}
/****************************************************************************/
IFILE* ifopen(char *fn, char *mode)
{
  IFILE *ifile;

  pthread_once ( &module_setup, module_init );
  LOCK_C_RTL
  ifile = malloc(sizeof(IFILE));
  UNLOCK_C_RTL
  ifile->fab = cc$rms_fab;
  if (strcmp(mode,"r") == 0) ifile->fab.fab$b_fac = FAB$M_GET;
  else if (strcmp(mode,"r+") == 0)
          ifile->fab.fab$b_fac = 
		FAB$M_DEL | FAB$M_GET | FAB$M_PUT | FAB$M_UPD;
    else {
      fprintf(stderr,"I-file open error - invalid mode \"%s\"",mode);
      exit(0);
    }
  ifile->fab.fab$l_fna = fn;
  ifile->fab.fab$b_fns = strlen(fn);
  ifile->fab.fab$b_shr = FAB$V_MSE | FAB$V_SHRPUT | FAB$V_SHRGET |
    FAB$V_SHRDEL | FAB$V_SHRUPD;
  pthread_mutex_lock ( &rms_io );
  ifile->status.rc = sys$open(&(ifile->fab),synch_ast,synch_ast);
  ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->fab.fab$l_sts );
  if (ifile->status.status_parts.severity != STS$K_SUCCESS) {
    LOCK_C_RTL
    free(ifile);
    UNLOCK_C_RTL
    return(NULL);
  }

  ifile->rab = cc$rms_rab;
  ifile->rab.rab$l_fab = &(ifile->fab);
  pthread_mutex_lock ( &rms_io );
  ifile->status.rc = sys$connect(&(ifile->rab),synch_ast,synch_ast);
  ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->rab.rab$l_sts );
  if (ifile->status.status_parts.severity != STS$K_SUCCESS) {
    sys$close(&(ifile->fab));
    LOCK_C_RTL
    free(ifile);
    UNLOCK_C_RTL
    return(NULL);
  }
  return ifile;
}

/****************************************************************************/
int ifclose(IFILE *ifile)
{
  pthread_mutex_lock ( &rms_io );
  ifile->status.rc = sys$close(&(ifile->fab),synch_ast,synch_ast);
  ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->fab.fab$l_sts );
  if (ifile->status.status_parts.severity != STS$K_SUCCESS) return EOF;
  LOCK_C_RTL
  free(ifile);
  UNLOCK_C_RTL
  return 0;
}

/****************************************************************************/
/* Check status of last ifile call and signal error.
 */
int iferror(IFILE *ifile)
{
  if (ifile->status.status_parts.severity == STS$K_SUCCESS)
    return 0;
  else {
    lib$signal(ifile->status.rc);
    return ifile->status.rc;
  }
}

/****************************************************************************/
/* Read keyed record from input file, locking for update.
 */
int ifread_rec(void *ptr, size_t size_of_item, size_t *size_read,
  IFILE *ifile, int index, void *key, int key_size)
{

  int i;
  if (key_size==0) {
    ifile->rab.rab$b_rac = RAB$C_SEQ;
    ifile->rab.rab$l_rop = RAB$M_WAT;
  }
  else {
    ifile->rab.rab$b_rac = RAB$C_KEY;
    ifile->rab.rab$l_rop = RAB$M_WAT | RAB$M_RLK;
    ifile->rab.rab$l_kbf = key;
    ifile->rab.rab$b_ksz = key_size;
  }
  ifile->rab.rab$l_ubf = ptr;
  ifile->rab.rab$w_usz = size_of_item;
  ifile->rab.rab$b_krf = index;

  pthread_mutex_lock ( &rms_io );
  ifile->status.rc = sys$get(&(ifile->rab),synch_ast,synch_ast);
  ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->rab.rab$l_sts );
  if (ifile->status.status_parts.severity == STS$K_SUCCESS) {
      *size_read = ifile->rab.rab$w_rsz;
  } else {
       *size_read = 0;
  }
  return ifile->status.rc;
}

/****************************************************************************/
/* write indexed file record.  Return value is VMS status of $UPDATE call.
 */
int ifwrite_rec(void *ptr, size_t size_of_item, IFILE *ifile)
{
  ifile->rab.rab$b_rac = RAB$C_KEY;
  ifile->rab.rab$l_rbf = ptr;
    ifile->rab.rab$w_rsz = size_of_item;
    ifile->rab.rab$l_rop = RAB$M_UIF | RAB$M_WAT;
    pthread_mutex_lock ( &rms_io );
    ifile->status.rc = sys$put(&(ifile->rab),synch_ast,synch_ast);
    ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->rab.rab$l_sts );
    return ifile->status.rc;
}
/****************************************************************************/
/* Update indexed file record.  Return value is VMS status of $UPDATE call.
 */
int ifupdate_rec(void *ptr, size_t size_of_item, IFILE *ifile)
{
  ifile->rab.rab$b_rac = RAB$C_KEY;
  ifile->rab.rab$l_rbf = ptr;
    ifile->rab.rab$w_rsz = size_of_item;
    ifile->rab.rab$l_rop = RAB$M_UIF | RAB$M_WAT;
    pthread_mutex_lock ( &rms_io );
    ifile->status.rc = sys$update(&(ifile->rab),synch_ast,synch_ast);
    ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->rab.rab$l_sts );
    return ifile->status.rc;
}
/****************************************************************************/
/* Delete current file record.  Return value is VMS status of $DELETE call.
 */
int ifdelete_rec( IFILE *ifile )
{
    int sys$delete();
    ifile->rab.rab$l_rop = 0;
    pthread_mutex_lock ( &rms_io );
    ifile->status.rc = sys$delete(&(ifile->rab),synch_ast,synch_ast);
    ifile->status.rc = synch_rms ( ifile->status.rc, &ifile->rab.rab$l_sts );
    return ifile->status.rc;
}
