/*
 * This program is run by a detached server process as part of a scheme to 
 * provide the same functionality as DECnet using mailboxes instead of actual
 * DECnet links.  The role of this program  within that scheme  matches that
 * of sys$system:netserver.exe - listen  for the next request and setup the
 * connection.
 *
 *
 *            +--------------+     +----------------+
 *            | ACP process  |---->: response MBX   :
 *            |              |     + - - - - - - - -+
 *            +- - - - - - - +     | client process |
 *         +->: Request MBX  :<----| (web server)   |
 *         |  +--------------+     +----------------+
 *         |             \                    |
 *         |              \                   |
 *         |           +----------------+     | (bi-directional)
 *         +-----------| server process |     |
 *                     | (cgi script)   |     |
 *                     + - - - - - - - -+     |
 *                     : link MBX       :<>---+
 *                     +----------------+
 *
 * Each of the three process involved has a mailbox that it creates
 * and is responsible for.
 *
 * ACP process/request MBX:
 *   The ACP process reads messages from the request MBX and takes the
 *   following actions:
 *	msg type:
 *        'Hello'  register the sending process PID as a client and
 *		   assign channel to its response MBX
 *
 *	  'Connect' Look for available server or create one, send
 *		    the resulting server's PID and link MBX unit number to
 *		    requesting client's response MBX.  Created servers
 *		    have their termination mailbox set to REQUEST MBX.
 *		    Client will then send connect message to server's
 *		    link MBX.
 *
 *	  'Accept' Mark sender as available to service a connect request.
 *		   (if sender is currently connected to a client, send
 *		    the client a disonnect message).
 *
 *	  'Disconnect' Wait x seconds for client to finish and then forceex.
 *
 *	  'DELPROC' Remove sender from process list and send connected client
 *		    a disconnect message.  Client that recieves disconnect
 *		    should cancel pending I/O to link MBX.
 *
 * Server process/link MBX:
 *   The server process executes a DCL command procedure that is an analogue
 *   to sys$system:netserver.com.  This procedure has a loop that
 *   the runs a program (analogue to netserver.exe) that creates the link MBX
 *   and sends an accept message to the request MBX and waits for a client
 *   to send a connect message.
 *
 * client process/response MBX:
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <descrip.h>
#include <iodef.h>
#ifndef IO$M_READERCHECK
#define IO$M_READERCHECK 0x100
#endif
#include <ssdef.h>
#include <dvidef.h>
#ifdef __DECC
#include <cmbdef.h>
#else
#ifndef CMB$M_WRITEONLY
#define CMB$M_READONLY 0x1
#define CMB$M_WRITEONLY 0x2
#endif
#endif
#include <jpidef.h>
#include <lckdef.h>

#include "mbxnet.h"

int SYS$QIOW(), SYS$ASSIGN(), SYS$DASSGN(), SYS$CREMBX(), SYS$FAO();
int LIB$GETDVI(), LIB$DO_COMMAND(), LIB$PUT_OUTPUT();

static short request_mbx, link_mbx;
static int link_unit;
static $DESCRIPTOR(request_mbx_name, "WWW_MBXNET_REQUEST");
static $DESCRIPTOR(cmd_line,"");

struct io_status_block { unsigned short status, length; long pid; };


/*****************************************************************************/
/* Create deadman lock.
 */
static int deadman_lock ( int master, long owner_pid )
{
    int LIB$GETJPI(), SYS$ENQW(), SYS$ENQ(), SYS$EXIT(), status;
    long lksb[4], code, pid, result;
    char lock_name[32];
    $DESCRIPTOR(lock_name_dx,"");
    /*
     * Get process ID and parent ID and generate lock name.
     */
    code = JPI$_OWNER;
    pid = 0;
    status = LIB$GETJPI ( &code, &pid, 0, &result, 0, 0 );
    if ( (status&1) == 0 ) return status;

    sprintf ( lock_name, "MBXNET_DEADMAN_%0x", master ? pid : owner_pid );
    lock_name_dx.dsc$w_length = strlen ( lock_name );
    lock_name_dx.dsc$a_pointer = lock_name;
    /*
     * Queue lock.
     */
    if ( master ) {
        status = SYS$ENQW ( 0, LCK$K_EXMODE, &lksb, 0, &lock_name_dx,
		0, 0, 0, 0, 0, 0 );
	if ( (status&1) == 1 ) status = lksb[0];
    } else {
        status = SYS$ENQ ( 0, LCK$K_EXMODE, &lksb, 0, &lock_name_dx,
	    0, SYS$EXIT, 44, 0, 0, 0 );
    }
    return status;
}
/*****************************************************************************/
/* Main program.
 *
 *  Command line arguments:
 *	(none)
 */
int main ( int argc, char **argv )
{
    int status, code, length;
    struct io_status_block iosb;
    netmbx_req_message message;
    char command[256], line[256];
    $DESCRIPTOR(report_fmt,"Connect from process !XL at !%D!/");
    $DESCRIPTOR(report,"");
    /*
     * Assign channel to the ACP's request mailbox.
     */
    status = SYS$ASSIGN ( &request_mbx_name, &request_mbx, 0, 0, 
	CMB$M_WRITEONLY );
    if ( (status&1) == 0 ) exit ( status );
    /*
     * Create private mailbox (read/write) and get its unit number.
     */
    status = SYS$CREMBX ( 0, &link_mbx, 4096, 4096, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) exit ( status );
    code = DVI$_UNIT;
    link_unit = 0;
    status = LIB$GETDVI ( &code, &link_mbx, 0, &link_unit, 0, 0 );
    if ( (status&1) == 0 ) exit ( status );
    /* printf("Mailbox chan is %d, unit is %d\n", link_mbx, link_unit ); */
    /*
     * Send message to request mailbox announcing we are ready for next
     * client.
     */
    message.gen.type = MSG$_CONFIRM;
    message.gen.unit = link_unit;
    status = SYS$QIOW ( 16, request_mbx, IO$_WRITEVBLK|IO$M_READERCHECK,
	&iosb, 0, 0, &message, 8, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) exit ( status );
    if ( (iosb.status&1) == 0 ) exit ( iosb.status );
    SYS$DASSGN ( request_mbx );
    /*
     * Set deadman lock to kill ourselves if no ACP.
     */
    status = deadman_lock ( 0, iosb.pid );
    if ( (status&1) == 0 ) exit ( status );
    /*
     * Client assigned to  us will send connect message to our mailbox,
     * read it.
     */
    for ( ; ; ) {
        status = SYS$QIOW ( 16, link_mbx, IO$_READVBLK, &iosb, 0, 0,
		&message, sizeof(message), 0, 0, 0, 0 );
	if ( (status&1) == 0 ) break;	/* QIO problem */

	if ( (iosb.status&1) == 0 ) continue;	/* ignore errors */	

	switch ( message.gen.type ) {
	    case MSG$_CONNECT:
		/*
		 * Client wishes connection.
		 */
		length = 0;
		report.dsc$w_length = sizeof(line);
		report.dsc$a_pointer = line;
		status = SYS$FAO ( &report_fmt, &length, &report,
			iosb.pid, 0 );
		report.dsc$w_length = length;
		LIB$PUT_OUTPUT ( &report );

		sprintf ( command, "call WWWEXEC _MBA%d:", link_unit );
		cmd_line.dsc$w_length = strlen ( command );
		cmd_line.dsc$a_pointer = command;
		status = LIB$DO_COMMAND ( &cmd_line );
		break;

	    case MSG$_DISCON:
		/*
		 * Client breaking connection, forcex server
		 */
		printf("Disconnect request from process %x\n", iosb.pid );
		return 2160;

	    default:
		printf("Unexpected message type (%d) sent from process %x\n",
			message.gen.type, iosb.pid );

		break;
	}
    }
    return status;
}
