#module NNTP_DECM "V5.5"

/*
**++
**  FACILITY:
**
**      NNTP_DECM
**
**  ABSTRACT:
**
**      This module contains the QIO interface to DECnet for the NNTP
**      server module. This differs from the NNTP_DEC code by allowing
**      multiple NNTP connections to be served from a single network
**      server process.
**
**      The build routines create DMSERVER.EXE, a NNTP server for DECnet
**
**  AUTHOR:
**
**      Geoff Huston
**
**  COPYRIGHT:
**
**      Copyright © 1988
**
**  MODIFICATION HISTORY:
**
**      V5.5     7-Oct-1988     GIH
**          Initial multi-threaded server inplementation. This version has NOT
**          been exhaustively tested - and bugs may still remain with parts of
**          this code.
**
**--
**/

                                        /* system include modules */
#include descrip
#include dvidef
#include iodef
#include msgdef
#include nfbdef
#include signal
#include ssdef
#include stdio

#define TEMP_MBX 0
#define MAX_MSG         128
#define MAX_NCB         110
#define BUF_QUO         128
                                        /* AST delivery types */
#define NET_RD          1
#define NET_WRT         2
#define NET_CMD         3
                                        /* processing queue types */
#define FREE_QUE        0
#define LIVE_QUE        1
#define ATTN_QUE        2
#define CMD_QUE         3

#define MAX_BUFFS       100
#define MAX_LINKS       32              /* Must be <= 32 for ffs/ffc to work */
#define SERVER_REQ      33

                                        /* read terminator types */
#define NO_INPUT        0
#define CMD_INPUT       1
#define FILE_INPUT      2

#define IDLE                0
#define READ_IN_PROGRESS    1
#define WRITE_IN_PROGRESS   2
#define LINK_ERROR          3

#define chk_ret(v)      if (!((status = (v)) & 1)) return(status)

int status,
    net_shut = 0,
    lct_alloc_mask = 0,
    mask_siz = 32,
    rtry = 10,
    *cur_buff,
    *netcmd_buff,
    dvi_unit,
    startup = 1;

unsigned int idle_server[2],
             idle_read[2];

int ast_routine (),
    read_timeout();


short int mbx_msg_len = MAX_MSG,
          mbx_chan,
          netdcl_chan;

char ncb[MAX_NCB],
     index,
     start_pos;


static $DESCRIPTOR(server_idle,"0 00:15:00.00");        /* 15 minutes */
static $DESCRIPTOR(read_idle,"0 00:10:00.00");          /* 10 minutes */
static $DESCRIPTOR(netcmd_mbx,"NETCMD_MBX");
static $DESCRIPTOR(net_device,"_NET:");
static $DESCRIPTOR(object_name,"NNTP");

struct dsc$descriptor_s nfb_desc;
struct dsc$descriptor_s ncb_desc;

struct {
        short int buff_len;
        short int code;
        int *ret_info;
        int ret_len;
        int terminator;
        } getdvi_itm = { 4, DVI$_UNIT, &dvi_unit, 0, 0 };

struct {
        char func;
        int terminator;
        } nfb = { NFB$C_DECLNAME, 0 };

struct mbx_blk {
        short int msg;                          /* 2 */
        short int unit;                         /* 2 */
        char name_info[MAX_NCB];                /* 110 */
        };                                      /* 114 */

struct ast_blk {
        char type;
        short int ndx;
        char unused;
        };

struct io_stat_blk {
        short int status ;
        short int msg_len ;
        int unused;
        } iosb;

struct quehdr {
        int flink;
        int blink;
        } *que_hdr;

struct wb {
        char *txt;
        struct wb *wnext;
        };

                                        /* this is a quadword multiple size */
struct buff_blk {
        int flink;                              /* 4 */
        int blink;                              /* 4 */
        union {
            int astid;                                  /* 4 */
            struct ast_blk ast;                         /* 4 */
            } cmplt_stat;                       /* 4 */
        struct io_stat_blk iosb;                /* 8 */
        union {
            char data[512];                             /* 512 */
            struct mbx_blk mbx_msg;                     /* 114 */
            } msg_area;                         /* 512 */
        int valid;                              /* 4 */
        } *buffers;

struct lct_blk {
        short int unit;
        short int channel;
        struct buff_blk *cur_buff;
        int state;
        struct wb *writenext, *writetail;
        struct wb *readnext, *readtail;
        int (*next_function)();
        int status;
        char active;
        int read_type;
        char *stop_read_text;
        } lct [MAX_LINKS];

globalvalue int LIB$_QUEWASEMP;
globalvalue int LIB$_NOTFOU;

#ifndef DEBUG
#define DEBUG 0
#endif

#if DEBUG
FILE *fpd;
#endif

/*
 *  main
 *
 *  server entry point
 */

main ()
{
    struct buff_blk *bufp;
    int lq_status,
        aq_status,
        cq_status;


#if DEBUG
    fpd = fopen("DMSRV.LOG","w");
    fprintf(fpd,"DMSRV\n");
#endif

    init();
    for (;;) {
        lq_status = check_live_queue();
        aq_status = check_attn_queue();
        cq_status = check_cmd_queue();
        if ((lq_status == LIB$_QUEWASEMP) && (aq_status == lq_status) &&
                (cq_status == lq_status)) {
            if (!(lct_alloc_mask || startup)) timeout_check();
            else sys$hiber();
            }
        }
}

/*
 *  Server idle timeout check
 */

server_timeout()
{
    exit(SS$_NORMAL);
}

timeout_check()
{

#if DEBUG
    fprintf(fpd,"Timeout check - no units active\n");
#endif

    sys$setimr(0,idle_server,server_timeout,SERVER_REQ);
    sys$hiber();
    sys$cantim(SERVER_REQ,0);
}

init()
{
    chk_ret(server_init(MAX_LINKS));
    chk_ret(init_vars());
    chk_ret(declare_network_object());
    return(status = issue_netcmd_read());
}

init_vars()
{
    int i;
    unsigned int qv;

    status = SS$_NORMAL;

    qv = malloc((sizeof *que_hdr) * 5);
    while (qv & 7) ++qv;
    que_hdr = qv;                               /* quad align it ! */
    que_hdr[0].flink = que_hdr[0].blink = 0;
    que_hdr[1].flink = que_hdr[1].blink = 0;

    qv = malloc((sizeof *buffers) * (MAX_BUFFS + 1));
    while (qv & 7) ++qv;
    buffers = qv;                               /* quad align it ! */

    nfb_desc.dsc$w_length = sizeof( nfb );
    nfb_desc.dsc$a_pointer = &nfb;
    nfb_desc.dsc$b_dtype = DSC$K_DTYPE_T;
    nfb_desc.dsc$b_class = DSC$K_CLASS_S;

    ncb_desc.dsc$w_length = sizeof(ncb);
    ncb_desc.dsc$a_pointer = &ncb;

    sys$bintim(&server_idle,idle_server);
    sys$bintim(&read_idle,idle_read);
    for (i = 0; i < MAX_BUFFS-1 ; i++) {
        buffers[i].valid = 0;
        chk_ret(lib$insqti(&buffers[i].flink,&que_hdr[FREE_QUE].flink,&rtry));
        }
    return(status = remque_buffer(FREE_QUE, &netcmd_buff));
}

declare_network_object()
{
    chk_ret(sys$crembx(TEMP_MBX,&mbx_chan,MAX_MSG,BUF_QUO,0,0,&netcmd_mbx));
    chk_ret(sys$assign(&net_device,&netdcl_chan,0,&netcmd_mbx));
    chk_ret(sys$qiow(0,netdcl_chan,IO$_ACPCONTROL,&iosb,0,0,
        &nfb_desc,&object_name,0,0,0,0));
    return(status = iosb.status);
}

/*  Find a link control table entry based on the unit.                     */

find_lct(unit,pindex)
    short int unit;
    char *pindex;
{
    int i;

    for (i = 0; i < MAX_LINKS; i++) {
        if (lct[i].unit == unit) {
            *pindex = i-1;
            return(SS$_NORMAL);
            }
        }
    return(status = LIB$_NOTFOU);
}

/*  Release a link control table (lct) entry.  Clear the lct entry and    *
 *  clear the corresponding bit in the allocation mask.  The mask is used *
 *  to scan the entire table in one instruction.                          */

release_lct(index)
    char index;
{
    lct_alloc_mask ^= ( 1 << index );
    lct[index].unit = 0;
    lct[index].channel = 0;
    lct[index].cur_buff = 0;
    return(SS$_NORMAL);
}

/*  Allocate the first available link control table (lct) entry.  Then     *
 *  set the corresponding bit in the allocation mask.                      */

allocate_lct(pindex)
    char *pindex;
{
    chk_ret(lib$ffc(&start_pos,&mask_siz,&lct_alloc_mask,pindex));
    lct_alloc_mask |= ( 1 << *pindex );
    return(status);
}

check_fatal_io(io_stat)
    short int io_stat;
{
    if ((io_stat != SS$_BUFFEROVF) && (io_stat != SS$_FILNOTACC)
        && (io_stat != SS$_INSFMEM)) io_stat = SS$_NORMAL;
    return(io_stat);
}

check_fatal_accept(io_stat)
    short int io_stat;
{
    if ((io_stat != SS$_DEVALLOC) && (io_stat != SS$_INSFMEM)
        && (io_stat != SS$_IVDEVNAM)) io_stat = SS$_NORMAL;
    return(io_stat);
}


/* ----------------- READ/WRITE QIO CALLS ------------------------------- */

/*  Issue a read on the network command mailbox.  The astid will inform    *
 *  the AST routine which buffer completed. This procedure is called at    *
 *  AST and Non-AST levels - the Non-AST call is startup only.             */

issue_netcmd_read ()
{
    struct buff_blk *netcb;

    netcb = netcmd_buff;
    netcb->cmplt_stat.astid = NET_CMD;
    return(sys$qio(0,mbx_chan,IO$_READVBLK,&netcb->iosb,&ast_routine,NET_CMD,
        &netcb->msg_area.mbx_msg,mbx_msg_len,0,0,0,0));
}

/*  Issue an asynchronous read on a logical link.  The astid will inform   *
 *  the AST routine which buffer completed.                                */

issue_link_read(index)
   char index;
{
    struct buff_blk *bp;

    lct[index].state = READ_IN_PROGRESS;
    bp = cur_buff;
    bp->cmplt_stat.ast.ndx = index;
    bp->cmplt_stat.ast.type = NET_RD;
    sys$setimr(0,idle_read,read_timeout,index);
    return(sys$qio(0,lct[index].channel,IO$_READVBLK,&bp->iosb,&ast_routine,
        bp->cmplt_stat.astid,bp->msg_area.data,sizeof(bp->msg_area.data),
        0,0,0,0));
}

/*  Issue an asynchronous write on a logical link.  The astid will inform  *
 *  the AST routine which buffer completed.                                */

issue_link_write(index)
    char index ;
{
    struct buff_blk *bufp;
    struct wb *tmp;

    bufp = lct[index].cur_buff;
    bufp->cmplt_stat.ast.ndx = index;
    bufp->cmplt_stat.ast.type = NET_WRT;
    tmp = lct[index].writenext;
    strcpy(bufp->msg_area.data,tmp->txt);
    if (tmp->wnext) lct[index].writenext = tmp->wnext;
    else {
        lct[index].writenext = 0;
        lct[index].writetail = 0;
        }
    lct[index].state = WRITE_IN_PROGRESS;
    free(tmp->txt);
    free(tmp);

#if DEBUG
    fprintf(fpd,"Issue_write (%d) %s",index,bufp->msg_area.data);
#endif


    return(sys$qio(0,lct[index].channel,IO$_WRITEVBLK,&bufp->iosb,&ast_routine,
        bufp->cmplt_stat.astid,bufp->msg_area.data,
        strlen(bufp->msg_area.data),0,0,0,0));
}

/* ----------------- ATTN QUEUE PROCESSING ------------------------- */

check_attn_queue()
{
    int status;

    while ((status = remque_buffer(ATTN_QUE, &cur_buff)) & 1)
        netcmd();
    return(status);
}

netcmd()
{
    short int unit;
    struct buff_blk *bufp;

    bufp = cur_buff;
    chk_ret(bufp->iosb.status);
    unit = bufp->msg_area.mbx_msg.unit;
    switch (bufp->msg_area.mbx_msg.msg) {
        case MSG$_ABORT     : status = link_failure(unit); break;
        case MSG$_CONFIRM   : status = not_used();         break;
        case MSG$_CONNECT   : status = establish_link();   break;
        case MSG$_DISCON    : status = link_failure(unit); break;
        case MSG$_EXIT      : status = link_failure(unit); break;
        case MSG$_INTMSG    : status = not_used();         break;
        case MSG$_PATHLOST  : status = link_failure(unit); break;
        case MSG$_PROTOCOL  : status = link_failure(unit); break;
        case MSG$_REJECT    : status = not_used();         break;
        case MSG$_THIRDPARTY: status = link_failure(unit); break;
        case MSG$_TIMEOUT   : status = link_failure(unit); break;
        case MSG$_NETSHUT   : status = shutdown();         break;
        default             : status = SS$_BADPARAM;       break;
        }
    return(status);
}

/* To establish a logical link, the incoming request must be accepted.     *
 * Five steps are required to accept a request:                            *
 *   1.  Copy the information portion of the NCB into a buffer.            *
 *   2.  Allocate a link control table entry.                              *
 *   3.  Assign a channel to the _NET device and associate the network     *
 *       command mailbox with the channel.                                 *
 *   4.  Get the unit number of the _NET channel to identify the network   *
 *       command messages.                                                 *
 *   5.  Issue to the _NET channel a QIO with the IO$_ACCESS function code *
 *       and the P2 parameter set to the address of the copied NCB         *
 *       information.                                                      */

establish_link ()
{
    int start,
        info_cnt,
        i,
        j,
        unit;
    struct buff_blk *bufp;

    bufp = cur_buff;
    i = bufp->msg_area.mbx_msg.name_info[0] + 1;
    info_cnt = bufp->msg_area.mbx_msg.name_info[i];
    start = i + 1;

    for (i = 0; i < info_cnt; i++)
        ncb[i] = bufp->msg_area.mbx_msg.name_info[start+i];
    ncb_desc.dsc$w_length = info_cnt;

    if ((status = allocate_lct(&index)) & 1) {
        chk_ret(sys$assign(&net_device,&lct[index].channel,0,&netcmd_mbx));
        getdvi_itm.ret_info = &unit;
        chk_ret(sys$getdviw(0,lct[index].channel,0,&getdvi_itm,&iosb,0,0,0));
        chk_ret(iosb.status);
        lct[index].unit = unit;
        chk_ret(sys$qiow(0,lct[index].channel,IO$_ACCESS,&iosb,0,0,
            0,&ncb_desc,0,0,0,0));
        if (iosb.status & 1) {
            lct[index].cur_buff = cur_buff;
            lct[index].cur_buff->valid = 1;
            lct[index].state = IDLE;
            startup = 0;

#if DEBUG
    fprintf(fpd,"New link - unit %d\n",index);
#endif

            return(status = server_init_unit(index));
            }
        chk_ret(check_fatal_accept(iosb.status));
        chk_ret(link_failure(lct[index].unit));
        }
    chk_ret(sys$qiow(0,netdcl_chan,IO$_ACCESS | IO$M_ABORT,&iosb,0,0,
        0,&ncb_desc,0,0,0,0));
    return(status = insque_buffer(FREE_QUE, cur_buff));
}

link_failure (unit)
    short int unit;
{
    if ((status = find_lct(unit, &index)) & 1) {

#if DEBUG
    fprintf(fpd,"Link failure for unit %d\n",index);
#endif

        if (sys$dassgn(lct[index].channel) & 1) {
            if (lct[index].cur_buff) lct[index].cur_buff->valid = 0;
            release_lct(index);
            }
        }
    status = insque_buffer(FREE_QUE, cur_buff);
    return(status);
}

shutdown()
{
    exit(1);
}

not_used ()
{
    return(insque_buffer(FREE_QUE, cur_buff));
}

/* ----------------- CMD QUEUE PROCESSING ---------------------------------*/

check_cmd_queue()
{
    struct buff_blk *bp;
    char index;
    int status;

    while ((status = remque_buffer(CMD_QUE, &cur_buff)) & 1) {
        bp = cur_buff;
        index = bp->cmplt_stat.ast.ndx;
        if (bp->valid) resume_server(index);
        else insque_buffer(FREE_QUE,cur_buff);
        }
    return(status);
}

resume_server(index)
    int index;
{
    if ((lct[index].state == IDLE) && (lct[index].next_function)) {
        lct[index].active = 1;
        (*lct[index].next_function)(index);
        lct[index].active = 0;
        if (lct[index].state == IDLE) {
            if (lct[index].next_function) return(issue_link_read(index));
            }
        else if (lct[index].state != LINK_ERROR) return;
        }

#if DEBUG
    fprintf(fpd,"Unit %d - signing off\n",index);
#endif

    sys$dassgn(lct[index].channel);
    if (lct[index].cur_buff) {
        lct[index].cur_buff->valid =0;
        insque_buffer(FREE_QUE, lct[index].cur_buff);
        }
    release_lct(index);
}

/* ----------------- LIVE QUEUE PROCESSING --------------------------------*/

/* This module removes the QIO completion entries from the LIVE QUEUE.     *
 * These QIOS denote completion of a read or write call on a logical link, *
 * and the appropriate handler is invoked for each IO type.                */

check_live_queue()
{
    struct buff_blk *bufp;
    char index;
    int status;

    while ((status = remque_buffer(LIVE_QUE, &cur_buff)) & 1) {
        bufp = cur_buff;
        if (!bufp->valid) insque_buffer(FREE_QUE,cur_buff);
        else {
            index = bufp->cmplt_stat.ast.ndx;
            switch (bufp->cmplt_stat.ast.type) {
                case NET_RD  : read_completed(index); break;
                case NET_WRT : write_completed(index); break;
                }
            }
        }
    return(status);
}

/* read completed -
 *  If there is more to read then requeue a read request, otherwise
 *  queue the buffer on the CMD_QUE.
 */

read_completed(index)
    char index;
{
    struct buff_blk *bp;
    struct wb *tmp;
    int i;
    char *t;

    bp = lct[index].cur_buff;
    if ((lct[index].status = bp->iosb.status) & 1) {
        i = bp->iosb.msg_len;
        t = bp->msg_area.data;
        if ((t[i-2] == '\r') && (t[i-1] == '\n')) {
            t[i-2] = '\n';
            t[--i] = '\0';
            }
        tmp = malloc(sizeof *tmp);
        tmp->wnext = 0;
        strncpy((tmp->txt = malloc(i + 1)),t,i);
        tmp->txt[i] = '\0';

#if DEBUG
    fprintf(fpd,"read_complete (%d) %s",index,tmp->txt);
#endif

        if (lct[index].readtail) lct[index].readtail->wnext = tmp;
        if (!lct[index].readnext) lct[index].readnext = tmp;
        lct[index].readtail = tmp;
        if ((lct[index].read_type == FILE_INPUT) && (strcmp(tmp->txt,".\n")))
            return(issue_link_read(index));
        lct[index].state = IDLE;
        }
    else lct[index].state = LINK_ERROR;
    insque_buffer(CMD_QUE,bp);
}

/* write completed -
 *  If there is more to write then requeue a write request, otherwise
 *  set the state to idle.
 */

write_completed(index)
    char index;
{
    struct buff_blk *bp;
    struct wb *tmp;

    bp = lct[index].cur_buff;
    if ((lct[index].status = bp->iosb.status) & 1) {
        if (lct[index].writenext) return(issue_link_write(index));
        lct[index].state = IDLE;
        }
    else {
        while (tmp = lct[index].writenext) {
            if (tmp->wnext) lct[index].writenext = tmp->wnext;
            else {
                lct[index].writenext = 0;
                lct[index].writetail = 0;
                }
            free(tmp->txt);
            free(tmp);
            }
        lct[index].state = LINK_ERROR;
        }
    if (!lct[index].active) {
        if ((lct[index].state == IDLE) && (lct[index].next_function))
            return(issue_link_read(index));
        else insque_buffer(CMD_QUE,bp);
        }
}

/*--------------- QUEUE HANDLING CODE (both AST and Non-AST level) --------*/

/*  Insert/Remove the buffer into the specified queue.                     *
 *  Relative queues are used because of the interlock supplied in the RTL  *
 *  routine.                                                               */

insque_buffer(queue,cur_buff)
    int *cur_buff;
    char queue;
{
    return(lib$insqti(cur_buff,&que_hdr[queue].flink,&rtry));
}

remque_buffer(queue,cur_buff)
    char queue;
    int *cur_buff;
{
    return(lib$remqhi(&que_hdr[queue].flink,cur_buff,&rtry));
}

/*--------------- AST HANDLER CODE ----------------------------------------*/

/* AST routine.  This module handles the completion ASTs of all the        *
 * asynchronous QIOs issued by the main process.  The ast_parameter passed *
 * to this module governs the processing.  The ast_parameter is actually   *
 * split into three parts:                                                 *
 *   ast_type   The type of QIO completion (NET_RD, NET_WRT, NET_CMD).     *
 *   index      The corresponding link control table entry (only valid for *
 *              NET_RD and NET_WRT).                                       *
 *   unused     Free space for later use.                                  *
 * Buffers are inserted into the LIVE_QUE or ATTN_QUE for processing in    *
 * the main module.  NET_CMD QIOs are reissued immediately to accomodate   *
 * multiple network requests.                                              */

ast_routine(ast_param)
    struct ast_blk ast_param;
{
    switch (ast_param.type) {
        case NET_RD  :
            sys$cantim(ast_param.ndx,0);
        case NET_WRT :
            status = insque_buffer(LIVE_QUE,lct[ast_param.ndx].cur_buff );
            break;
        case NET_CMD :
            if (((status = insque_buffer(ATTN_QUE,netcmd_buff)) & 1)
                  && ((status = remque_buffer(FREE_QUE,&netcmd_buff)) & 1))
                status = issue_netcmd_read();
            break;
        default      :
            break;
        }
    if ((!(status & 1)) || (!((status = sys$wake(0,0)) & 1)))
        exit(status);
}

read_timeout(index)
    int index;
{
#if DEBUG
    fprintf(fpd,"Unit %d - read timed out\n",index);
#endif
}

/*---------------- SERVER INTERFACE ROUTINES ------------------------------*/

read_net(buffer,size,index)
    char *buffer;
    int size;
    int index;
{
    char *rtn_txt,
         *sav_ptr;
     struct wb *tmp;

    check_attn_queue();
    check_live_queue();

    if (!lct[index].readnext) strcpy(buffer,lct[index].stop_read_text);
    else {
        rtn_txt = lct[index].readnext->txt;
        strcpy(buffer,rtn_txt);
        tmp = lct[index].readnext;
        lct[index].readnext = tmp->wnext;
        if (!lct[index].readnext) lct[index].readtail = 0;
        free(tmp->txt);
        free(tmp);
        }
    return(strlen(buffer));
}

write_net(s,index)
    char *s;
    int index;
{
    struct wb *tmp;

    check_attn_queue();
    check_live_queue();
    if (lct[index].state == LINK_ERROR) return;
    tmp = malloc(sizeof *tmp);
    tmp->wnext = 0;
    strcpy(tmp->txt = malloc(strlen(s) + 1),s);
    if (lct[index].writetail) lct[index].writetail->wnext = tmp;
    if (!lct[index].writenext) lct[index].writenext = tmp;
    lct[index].writetail = tmp;
    if (lct[index].state == IDLE) issue_link_write(index);
}

/*
 *  next_call
 *
 *  Set the context block to reference the next state of the server for this
 *  connection.
 */

next_call(index,func,type)
    int index;
    int (*func)();
    int type;
{
    lct[index].next_function = func;
    lct[index].read_type = type;
    if (type == FILE_INPUT) lct[index].stop_read_text = ".\n";
    else lct[index].stop_read_text = "QUIT\n";
}
