/*
 * PDPAR.C - routines to support parallel i/o
 *
 *
 * Source Version: 9.0
 * Software Release #92-0043
 *
 */

#include "cpyright.h"

#include "pdb.h"

#define N_PFM_FILES  8;
#define N_PFM_INC    4;

#define IS_STD_IO(_f)                                                      \
    (((_f)->stream == stdout) ||                                           \
     ((_f)->stream == stdin) ||                                            \
     ((_f)->stream == stderr))

#ifdef HAVE_THREADS

typedef struct PD_Pfile_manager_s PD_Pfile_manager;

struct PD_Pfile_manager_s
   {int available;
    off_t addr; };

#define DISK(t, x)  ((x)->f_addr[t].diskaddr)

SC_THREAD_LOCK(PD_Pfile_manager_lock);
SC_THREAD_LOCK(PD_io_lock);

static PD_Pfile_manager
 *Pfile_manager = NULL;

static int
 nfilesx = 0;

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_RL_PFILE - release a PD_Pfile */

static void _PD_rl_pfile(pfile)
   PD_Pfile *pfile;
   {

    SFREE(pfile->f_addr);
    SFREE(pfile);

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PTELL - do an ftell on the parallel file
 *           - binds to pio_tell_hook
 */

static off_t _PD_ptell(stream)
   FILE *stream;
   {PD_Pfile *fp;
    off_t addr;
    SC_THREAD_ID(_t_index);

    addr = -1;

    fp = (PD_Pfile *) stream;
    if (fp == NULL)
       addr = -1;

    else if (IS_STD_IO(fp))
       addr = F_TELL(fp->stream);

    else
       addr = DISK(_t_index, fp);

    return(addr);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PSETVBUF - set the parallel file buffer
 *              - binds to pio_setvbuf_hook
 */

static int _PD_psetvbuf(stream, bf, type, size)
   FILE *stream;
   char *bf;
   int type;
   size_t size;
   {int ret;

    if (stream == NULL)
       ret = FALSE;

    else
       ret = setvbuf(stream, bf, type, size);

    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PCLOSE - close the parallel file
 *            - binds to pio_close_hook
 */

static int _PD_pclose(stream)
   FILE *stream;
   {PD_Pfile *fp;
    int ret;

    ret = 0;
    fp  = (PD_Pfile *) stream;

    if (fp == NULL)
       ret = EOF;

    else if (IS_STD_IO(fp))
       ret = fclose(fp->stream);

    else
       {ret = fclose(fp->stream);
	_PD_rl_pfile(fp);};
        
    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PFLUSH - do an fflush on the parallel file
 *            - binds to pio_flush_hook
 */

static int _PD_pflush(stream)
   FILE *stream;
   {int ret;
    PD_Pfile *fp;

    ret = FALSE;
    fp  = (PD_Pfile *) stream;
    if (fp == NULL)
       ret = TRUE;

    else if (IS_STD_IO(fp))
       ret = fflush(fp->stream);

    else  /* when buffered i/o is introduced this will be changed */
       {SC_LOCKON(PD_io_lock);
        ret = fflush(fp->stream);
        SC_LOCKOFF(PD_io_lock);}

    return(ret);}


/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PSEEK - do an fseek on the parallel file
 *           - binds to pio_seek_hook
 */

static int _PD_pseek(stream, offset, whence)
   FILE *stream;
   off_t offset;
   int whence;
   {int ret;
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    ret = 0;
    fp  = (PD_Pfile *) stream;
    if (fp == NULL)
       ret = -1;

    else if (IS_STD_IO(fp))
       ret = F_SEEK(fp->stream, offset, whence);

    else
       {switch (whence)
           {case SEEK_SET :
		 DISK(_t_index, fp) = offset;
                 break;

            case SEEK_CUR :
		 DISK(_t_index, fp) += offset;
                 break;

            case SEEK_END :
                 SC_LOCKON(PD_io_lock);
                 ret = F_SEEK(fp->stream, offset, SEEK_END);
		 DISK(_t_index, fp) = F_TELL(fp->stream);
                 SC_LOCKOFF(PD_io_lock);
                 break;

            default :
	         ret = -1;
	         break;};};

    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PREAD - do an fread on the parallel file
 *           - binds to pio_read_hook
 */

static size_t _PD_pread(s, nbi, ni, stream)
   char *s;
   size_t nbi, ni;
   FILE *stream;
   {PD_Pfile *fp;
    size_t nbw;
    SC_THREAD_ID(_t_index);

    fp  = (PD_Pfile *) stream;
    if (fp == NULL)
       nbw = 0;

    else if (IS_STD_IO(fp))
       nbw = fread(s, nbi, ni, fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
        nbw = fread(s, nbi, ni, fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the current address */
        DISK(_t_index, fp) += nbi*ni;};

    return(nbw);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PWRITE - do an fwrite on the parallel file
 *            - binds to pio_write_hook
 */

static size_t _PD_pwrite(s, nbi, ni, stream)
   char *s;
   size_t nbi, ni;
   FILE *stream;
   {long nbw;
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    fp  = (PD_Pfile *) stream;
    if ((fp == NULL) || (nbi*ni == 0))
       nbw = 0;

    else if (IS_STD_IO(fp))
       nbw = fwrite(s, nbi, ni, fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
        nbw = fwrite(s, nbi, ni, fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the current address */
        DISK(_t_index, fp) += nbi*ni;};

    return(nbw);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PPRINTF - do an fprintf on the parallel file
 *             - binds to pio_puts_hook
 */

#ifdef PCC

int _PD_pprintf(stream, fmt, va_alist)
   FILE *stream;
   char *fmt;
   va_dcl

#endif

#ifdef ANSI

int _PD_pprintf(FILE *stream, char *fmt, ...)

#endif

   {size_t ni, nw;
    char _PD_pbf[LRG_TXT_BUFFER];
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    nw = 0;

    fp  = (PD_Pfile *) stream;
    
    if (fp != NULL)
       {SC_VA_START(fmt);
	SC_VSPRINTF(_PD_pbf, fmt);
	SC_VA_END;

        ni = strlen(_PD_pbf);

        SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
	nw = fwrite(_PD_pbf, (size_t) 1, ni, fp->stream);
        SC_LOCKOFF(PD_io_lock)

/* adjust the current address */
        DISK(_t_index, fp) += nw;};

    return((int) nw);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PPUTS - do an fputs on the parallel file
 *           - binds to pio_puts_hook
 */

static int _PD_pputs(s, stream)
   char *s;
   FILE *stream;
   {long nbw;
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    fp = (PD_Pfile *) stream;
    if (fp == NULL)
       nbw = 0;

    else if (IS_STD_IO(fp))
       nbw = fputs(s, fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
	nbw = fputs(s, fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the current address */
        DISK(_t_index, fp) += nbw;};

    return(nbw);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PGETS - do an fgets on the parallel file
 *           - binds to pio_gets_hook
 */

static char *_PD_pgets(s, nc, stream)
   char *s;
   int nc;
   FILE *stream;
   {char *ps;
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    ps = NULL;
    fp = (PD_Pfile *) stream;
    if (fp == NULL)
       ps = NULL;

    else if (IS_STD_IO(fp))
       ps = fgets(s, nc, fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
        ps = fgets(s, nc, fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the local idea of the current disk address */
        if (ps != NULL)
	   DISK(_t_index, fp) += strlen(ps);};

    return(ps);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PGETC - do an fgetc on the parallel file
 *           - binds to pio_getc_hook
 */

static int _PD_pgetc(stream)
   FILE *stream;
   {int c;
    PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    fp = (PD_Pfile *) stream;
    if (fp == NULL)
       c = EOF;

    else if (IS_STD_IO(fp))
       c = fgetc(fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
	c = fgetc(fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the current address */
        DISK(_t_index, fp) += 1;};

    return(c);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PUNGETC - do an fungetc on the parallel file
 *             - binds to pio_ungetc_hook
 */

static int _PD_pungetc(c, stream)
   int c;
   FILE *stream;
   {PD_Pfile *fp;
    SC_THREAD_ID(_t_index);

    fp = (PD_Pfile *) stream;
    if (fp == NULL)
       c = 0;

    else if (IS_STD_IO(fp))
       c = ungetc(c, fp->stream);

    else
       {SC_LOCKON(PD_io_lock);
        F_SEEK(fp->stream, DISK(_t_index, fp), SEEK_SET);
	c = ungetc(c, fp->stream);
        SC_LOCKOFF(PD_io_lock);

/* adjust the current address */
        DISK(_t_index, fp) -= 1;};

    return(c);}

/*--------------------------------------------------------------------------*/

/*                      PARALLEL FILE MANAGER ROUTINES                      */

/*--------------------------------------------------------------------------*/

/* _PD_PFM_GETSPACE - return the next disk address available for writing
 *                  - and increment it to reserve space in threaded mode
 */

off_t _PD_pfm_getspace(file, nbytes)
   PDBfile *file;
   size_t nbytes;
   {off_t ret;
    PD_Pfile *pfile;
    int id;
    SC_THREAD_ID(_t_index);

    pfile = (PD_Pfile *) (file->stream);
    id = pfile->id;

    SC_LOCKON(PD_Pfile_manager_lock);
    ret = Pfile_manager[id].addr;
    if (nbytes > 0)
       Pfile_manager[id].addr += nbytes;
    SC_LOCKOFF(PD_Pfile_manager_lock);

    DISK(_t_index, pfile) = ret;

    return(ret);}
    
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PFM_SETADDR - set the next available address for writing to ADDR */

void _PD_pfm_setaddr(file, addr)
   PDBfile *file;
   off_t addr;
   {PD_Pfile *pfile;
    int id;
    SC_THREAD_ID(_t_index);
    
    pfile = (PD_Pfile *) (file->stream);
    id    = pfile->id;

    SC_LOCKON(PD_Pfile_manager_lock);
    Pfile_manager[id].addr = addr;
    SC_LOCKOFF(PD_Pfile_manager_lock);

    DISK(_t_index, pfile) = addr;

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PFM_INIT - initialize the file manager for parallel settings */

void _PD_pfm_init()
   {int i;
    PD_Pfile_manager *pf;

    SC_LOCKON(PD_Pfile_manager_lock);

    if (Pfile_manager == NULL)
       {nfilesx = N_PFM_FILES;

        Pfile_manager = pf = NMAKE_N(PD_Pfile_manager, nfilesx,
                                     "_PD_PFM_INIT:Pfile_manager");

        for(i = 0; i < nfilesx; i++, pf++)
	  {pf->available = TRUE;
	   pf->addr      =    0;};};

     SC_LOCKOFF(PD_Pfile_manager_lock);

     return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PFM_ADD - add a file to the list of managed files */

void _PD_pfm_add(file, start_addr)
   PDBfile *file;
   off_t start_addr;
   {int i, pf_insert;
    PD_Pfile *pfile;
    PD_Pfile_manager *pf;

    pfile = (PD_Pfile *) (file->stream);

    SC_LOCKON(PD_Pfile_manager_lock);
    for (pf = Pfile_manager, pf_insert = 0;
	 pf_insert < nfilesx; 
	 pf++, pf_insert++)
        if (pf->available) 
           break;

    if (pf_insert == nfilesx)
       {nfilesx += N_PFM_INC;
        NREMAKE_N(Pfile_manager, PD_Pfile_manager, nfilesx);

        pf = &Pfile_manager[pf_insert];
        for (i = pf_insert; i < nfilesx; pf++, i++)
	    {pf->available = TRUE;
	     pf->addr      =    0;};}
                    
    Pfile_manager[pf_insert].available = FALSE;
    Pfile_manager[pf_insert].addr      = start_addr;

    pfile->id = pf_insert;

    SC_LOCKOFF(PD_Pfile_manager_lock);
    
    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_PFM_REMOVE - remove the given file from the list of managed files */

void _PD_pfm_remove(file)
   PDBfile *file;
   {int id;
    PD_Pfile *pfile;

    pfile = (PD_Pfile *) (file->stream);
    id    = pfile->id;

    SC_LOCKON(PD_Pfile_manager_lock);

    Pfile_manager[id].available = TRUE;
    Pfile_manager[id].addr      =    0;

    SC_LOCKOFF(PD_Pfile_manager_lock);
 
    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_SMP_INIT - set up the file for smp parallel i/o */

int _PD_smp_init(file)
   PDBfile *file;
   {PD_Pfile *pfile;
    SC_address *tmp;
    int i;
    SC_THREAD_ID(_t_index);

    if (_PD_nthreads <= 0)
       {sprintf(PD_ERR(_t_index),
		"ERROR: THREADS NOT INITIALIZED - _PD_SMP_INIT\n");
        return(FALSE);};

    pfile = FMAKE(PD_Pfile, "_PD_SMP_OPEN:pfile");

    pfile->f_addr = tmp = FMAKE_N(SC_address, _PD_nthreads,
                                  "_PD_SMP_INIT:f_addr");

    for (i = 0; i < _PD_nthreads; i++, tmp++)
        tmp->diskaddr = 0;

/* no buffered i/o for now */
    pfile->bf     = NULL; 
    pfile->bf_len =   0L;

    pfile->stream = file->stream;
    file->stream  = (FILE *) pfile;

    if (Pfile_manager == NULL)
       _PD_pfm_init();

    _PD_pfm_add(file, 0);
    
    return(TRUE);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_SMP_TERM - Release smp resources for file */

void _PD_smp_term(file)
   PDBfile *file;
   {PD_Pfile *pfile;

    pfile = (PD_Pfile *) (file->stream);

    _PD_rl_pfile(pfile);
    _PD_pfm_remove(file);

    return;}

/*--------------------------------------------------------------------------*/

#endif

/*--------------------------------------------------------------------------*/

/* PD_INIT_THREADS - initialize the library for pthreads
 *                 - must be called by only one thread.
 */

int PD_init_threads(nthreads, tid)
   int nthreads;
   PFVoid tid;
   {

#ifdef HAVE_THREADS

    int i;
    PD_thread_state *p;

    nthreads = max(1L, nthreads);

    if ((SC_n_threads != 0) && (SC_n_threads != nthreads))
       return(FALSE);
    else
       {SC_configure_mm(128L, 2000000L, 65536L, 1.2);
        SC_init_threads(nthreads, tid);}

    PD_threads = NMAKE_N(PD_thread_state, nthreads, "PD_INIT_THREADS:PD_threads");
    if (PD_threads == NULL)
       return(FALSE);

    p = PD_threads;

    for (i = 0; i < nthreads; p++, i++)
        {p->PD_buffer_size   = PD_buffer_size;
         p->PD_DIM           = PD_DIM;
         p->INT_STANDARD     = INT_STANDARD;
         p->REQ_STANDARD     = REQ_STANDARD;
         p->INT_ALIGNMENT    = INT_ALIGNMENT;
         p->REQ_ALIGNMENT    = REQ_ALIGNMENT;
         p->PD_vif           = PD_vif;
         p->pdb_wr_hook      = pdb_wr_hook;
         p->pdb_rd_hook      = pdb_rd_hook;
         p->pio_open_hook    = (PFfopen) fopen;
         p->pio_tell_hook    = (PFftell) _PD_ptell;
         p->pio_read_hook    = (PFfread) _PD_pread;
         p->pio_write_hook   = (PFfwrite) _PD_pwrite;
         p->pio_printf_hook  = (PFfprintf) _PD_pprintf;
         p->pio_setvbuf_hook = (PFsetvbuf) _PD_psetvbuf;
         p->pio_close_hook   = (PFfclose) _PD_pclose;
         p->pio_seek_hook    = (PFfseek) _PD_pseek;
         p->pio_puts_hook    = (PFfputs) _PD_pputs;
         p->pio_getc_hook    = (PFfgetc) _PD_pgetc;
         p->pio_ungetc_hook  = (PFungetc) _PD_pungetc;
         p->pio_flush_hook   = (PFfflush) _PD_pflush;
         p->pio_gets_hook    = (PFfgets) _PD_pgets;}

    _PD_nthreads = nthreads;

/* initialize the packages */
    _PD_pdb_pinit();
    _PD_net_pinit();
    _PD_fia_pinit();
    _PD_low_pinit();
    _PD_rdwr_pinit();
    _PD_szof_pinit();
    _PD_dir_pinit();
    _PD_memb_pinit();

    return(TRUE);} 
#else
    return(FALSE);}
#endif

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_INIT_THREADS_ARG - initialize the number of threads by scanning
 *                     - for a designated key in a pattern like
 *                     -  .... <key> <# threads> ...
 */

int PD_init_threads_arg(c, v, key, tid)
   int c;
   char **v, *key;
   PFVoid tid;   
   {int i, nt;

    nt = 0;
    for (i = 1; i < c; i++)
        {if (strcmp(v[i], key) == 0)
	    {nt = SC_stoi(v[++i]);
	     break;};};

    PD_init_threads(nt, tid);

    return(nt);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

