/* .TITLE DB_SERVER - Database server program * * .IDENT 'X-4' */ /***************************************************************************/ /* */ /* COPYRIGHT (c) 1978, 1980, 1982, 1984 BY */ /* DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS. */ /* ALL RIGHTS RESERVED. */ /* */ /* 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 */ /* TRANSFERRED. */ /* */ /* THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE */ /* AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT */ /* CORPORATION. */ /* */ /* DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE OR RELIABILITY OF ITS */ /* SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL. */ /* */ /* */ /***************************************************************************/ /*++ */ /* FACILITY: DECnet nontransparent task-to-task communication. */ /* ABSTRACT: DB_SERVER */ /* This program demonstrates how to declare a known network object and * * service and manage multiple connect requests. It is a simple * * implementation of task-to-task communication using DECnet * * nontransparently. DB_SERVER accesses the database DB_USER, based on * * the name key supplied in the request buffer, and sends a response to * * the requesting node. */ /* ENVIRONMENT: User mode */ /* AUTHOR: Scott A. Shurts, CREATION DATE: 10-Nov-1986 */ /* MODIFIED BY: */ /* */ /* X-4 JC00001 Jim Colombo 13-Dec-1991 */ /* Added support for new CPU architecture. */ /* */ /* X-3 SAS0210 Scott A. Shurts 06-Nov-1989 */ /* Add missing "return return_status" in initialize_variables. It */ /* was detected when the compiler optimization changed behavior. */ /* */ /* X-2 SAS0039 Scott A. Shurts 06-Sept-1988 */ /* Insert align macro for explicit quadword alignment of queue */ /* header and buffers. This was previously done implicitly by */ /* the compiler. Also change the use of the return_status */ /* variable in the ast_routine. Change the definition of the */ /* pointers cur_buff and netcmd_buff to reference the buff_blk */ /* structure. */ /* */ /* INCLUDE EXTERNAL CONSTANTS */ #include #include #include #include #include #pragma nomember_alignment #include #include #include #pragma member_alignment #include #include #include /* DEFINE LOCAL CONSTANTS */ #define TEMP_MBX 0 #define MAX_BUFFS 100 #define MAX_LINKS 32 /* Must be <= 32 for ffs/ffc to work */ #define MAX_NAME 20 #define MAX_ACCOUNT 11 #define MAX_PHONE 14 #define MAX_ADDRESS 30 #define MAX_LOCATION 30 #define MAX_REC_SIZE MAX_NAME+MAX_ACCOUNT+MAX_PHONE+MAX_ADDRESS+MAX_LOCATION+4 #define BUF_QUO 128 #define MAX_MSG 128 #define MAX_NCB 110 /* DEFINE AST COMPLETION TYPES */ #define NET_RD 1 #define NET_WRT 2 #define NET_CMD 3 /* DEFINE QUEUE HEADER CONSTANTS */ #define FREE_QUE 0 #define LIVE_QUE 1 /* DEFINE GLOBAL DATA STRUCTURES */ #pragma nomember_alignment struct data_blk { char name[MAX_NAME]; char account[MAX_ACCOUNT]; char phone[MAX_PHONE]; char address[MAX_ADDRESS]; char location[MAX_LOCATION]; int status; char spare[7]; }; struct mbx_blk { short int msg; short int unit; char name_info[MAX_NCB]; }; struct ast_blk { char type; short int ndx; char unused; }; struct io_stat_blk { short int status ; short int msg_len ; int unused; }; struct buff_blk { int flink; int blink; union { int astid; struct ast_blk ast; } cmplt_stat; struct io_stat_blk iosb; union { struct data_blk data; struct mbx_blk mbx_msg; } msg_area; } ; /* DECLARE GLOBAL DATA */ _align (quadword) globaldef struct { int flink; int blink; } que_hdr[2] = { 0, 0, 0, 0 }; _align (quadword) globaldef struct buff_blk buffers [MAX_BUFFS] ; globaldef struct lct_blk { short int unit; short int channel; int cur_buff; } lct [MAX_LINKS]; /* DECLARE EXTERNAL CONSTANTS */ globalvalue int LIB$_QUEWASEMP; globalvalue int LIB$_NOTFOU; /* DECLARE AST ROUTINE */ int ast_routine (); /* DECLARE DATA */ #pragma member_alignment struct FAB db_fab; struct RAB db_rab; struct io_stat_blk iosb ; struct { char func; int terminator; } nfb = { NFB$C_DECLNAME, 0 }; int dvi_unit; 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 }; int return_status, lct_alloc_mask = 0, mask_siz = 32, rtry = 10; struct buff_blk *cur_buff, *netcmd_buff; short int netdcl_chan, mbx_chan, mbx_msg_len = MAX_MSG; char index, start_pos, net_shut = 0; char ncb[MAX_NCB]; char db_file[19] = "DB_DIR:DB_USER.IDX"; /* BUILD DESCRIPTORS */ static $DESCRIPTOR ( net_device, "_NET:" ); static $DESCRIPTOR ( object_name, "DB_SERVER" ); static $DESCRIPTOR ( netcmd_mbx, "NETCMD_MBX" ); struct dsc$descriptor_s nfb_desc; struct dsc$descriptor_s ncb_desc; main () /* After initialization, DB_SERVER processes requests until a NETSHUT * * message is received in the network command mailbox. Asynchronous * * processing, which permits several logical links to be serviced * * concurrently, is achieved through the use of asynchronous system traps * * (ASTs). */ { return_status = initialization () ; while ( return_status & STS$M_SUCCESS && !net_shut ) { return_status = remque_buffer ( LIVE_QUE, &cur_buff ) ; if ( return_status & STS$M_SUCCESS ) { switch ( cur_buff->cmplt_stat.ast.type ) { case NET_RD : return_status = process_request (); break; case NET_WRT : return_status = process_response (); break; case NET_CMD : return_status = process_netcmd (); break; default : return_status = SS$_BADPARAM; break; } } else if ( return_status == LIB$_QUEWASEMP ) return_status = sys$hiber (); } exit ( return_status ); } initialization () /* Set up descriptors, initialize variables, queues and buffers, declare * /* the process as a network receiver, open the database, and issue a read * /* to the network command mailbox. */ { return_status = initialize_variables (); if ( return_status & STS$M_SUCCESS ) { return_status = declare_network_object (); if ( return_status & STS$M_SUCCESS ) { return_status = open_database (); if ( return_status & STS$M_SUCCESS ) return_status = issue_netcmd_read (); } } return return_status; } initialize_variables () { int i; return_status = SS$_NORMAL; net_shut = 0; 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; /* Insert all buffers into the FREE_QUE for later use. */ for ( i = 0; i < MAX_BUFFS-1 && return_status & STS$M_SUCCESS; i++ ) return_status = lib$insqti ( &buffers[i].flink, &que_hdr[FREE_QUE].flink, &rtry ); if ( return_status & STS$M_SUCCESS ) /*Remove one buffer to issue a read to the network command mailbox.*/ return_status = remque_buffer ( FREE_QUE, &netcmd_buff ); return return_status; } declare_network_object () /* Declare the DB_SERVER process as a know network object to allow the * * reception of multiple inbound connect requests. The three steps are: * * 1. Create a temporary mailbox to receive network command messages. * * 2. Assign a channel to the _NET device and associate the temporary * * mailbox with it. * * 3. Issue a QIO with a function code of IO$_ACPCONTROL, the P1 * * parameter set to the address of the network function block (nfb), * * and the P2 parameter set to the address of the descriptor * * containing the object name to be declared. */ { return_status = sys$crembx ( TEMP_MBX, &mbx_chan, MAX_MSG, BUF_QUO, 0, 0, &netcmd_mbx ); if ( return_status & STS$M_SUCCESS ) { return_status = sys$assign ( &net_device, &netdcl_chan, 0, &netcmd_mbx ); if ( return_status & STS$M_SUCCESS ) { return_status = sys$qiow ( 0, netdcl_chan, IO$_ACPCONTROL, &iosb, 0, 0, &nfb_desc, &object_name, 0, 0, 0, 0 ); if ( return_status & STS$M_SUCCESS ) return_status = iosb.status; } } return return_status; } open_database () /* Initialize the FAB and RAB before opening and connecting a stream. */ { db_fab = cc$rms_fab; db_rab = cc$rms_rab; db_fab.fab$b_fac = FAB$M_GET; db_fab.fab$l_fna = &db_file; db_fab.fab$b_fns = sizeof (db_file); db_fab.fab$w_mrs = MAX_REC_SIZE; db_fab.fab$b_org = FAB$C_IDX; db_fab.fab$b_rat = FAB$M_CR; db_fab.fab$b_rfm = FAB$C_FIX; db_rab.rab$l_fab = &db_fab; db_rab.rab$b_rac = RAB$C_KEY; db_rab.rab$b_krf = 0; db_rab.rab$b_ksz = MAX_NAME; db_rab.rab$w_usz = sizeof ( buffers[0].msg_area.data ); return_status = sys$open ( &db_fab ); if ( return_status & STS$M_SUCCESS ) return_status = sys$connect ( &db_rab ); return return_status; } process_request () /* A database query request has been received on a logical link. Read the * * database and issue a write to return the data. */ { char index; index = cur_buff->cmplt_stat.ast.ndx; if ( cur_buff->iosb.status & STS$M_SUCCESS ) { return_status = read_database (); if ( return_status & STS$M_SUCCESS ) return_status = issue_link_write ( index ); } else { /* The I/O completed with a failure. Check for network failure * * and await the DECnet mailbox message if it was a network * * failure. */ return_status = check_fatal_io ( cur_buff->iosb.status ); if ( return_status & STS$M_SUCCESS ) return_status = insque_buffer ( FREE_QUE, cur_buff ); } return return_status; } process_response () /* The response to a user's request has completed. Issue another read * * on the logical link for the next request. */ { char index; index = cur_buff->cmplt_stat.ast.ndx; if ( cur_buff->iosb.status & STS$M_SUCCESS ) return_status = issue_link_read ( index ); else { /* The I/O completed with a failure. Check for network failure * * and await the DECnet mailbox message if it was a network * * failure. */ return_status = check_fatal_io ( cur_buff->iosb.status ); if ( return_status & STS$M_SUCCESS ) return_status = insque_buffer ( FREE_QUE, cur_buff ); } return return_status; } process_netcmd () /* A network command message was received in the network command mailbox. * * Each request is dispatched into one of four groups: * * link_failure A failure has occurred on the link. * * establish_link A connect initiate message was received. * * not_used A message that is not used in this program was * * received. * * shutdown A NETSHUT message was received, indicating that DECnet* * is shutting down and the program should terminate. */ { short int unit; if ( cur_buff->iosb.status & STS$M_SUCCESS ) { unit = cur_buff->msg_area.mbx_msg.unit; switch ( cur_buff->msg_area.mbx_msg.msg ) { case MSG$_ABORT : return_status = link_failure(unit); break; case MSG$_CONFIRM : return_status = not_used(); break; case MSG$_CONNECT : return_status = establish_link(); break; case MSG$_DISCON : return_status = link_failure(unit); break; case MSG$_EXIT : return_status = link_failure(unit); break; case MSG$_INTMSG : return_status = not_used(); break; case MSG$_PATHLOST : return_status = link_failure(unit); break; case MSG$_PROTOCOL : return_status = link_failure(unit); break; case MSG$_REJECT : return_status = not_used(); break; case MSG$_THIRDPARTY : return_status = link_failure(unit); break; case MSG$_TIMEOUT : return_status = link_failure(unit); break; case MSG$_NETSHUT : return_status = shutdown (); break; default : return_status = SS$_BADPARAM; break; } } else return_status = cur_buff->iosb.status; return return_status; } link_failure (unit) short int unit ; /* Cleanup of a logical link involves these steps: * * 1. Find the link in the link control table (lct) based on the unit. * * 2. Deassign the channel. Otherwise, the link remains in a closed * * state and the _NET device remains allocated. * * 3. Release the lct entry. * * 4. Insert the buffer into the FREE_QUE for later use. */ { return_status = find_lct ( unit, &index ); if ( return_status & STS$M_SUCCESS ) { return_status = sys$dassgn ( lct[index].channel ); if ( return_status & STS$M_SUCCESS ) { return_status = release_lct ( index ); return_status = insque_buffer ( FREE_QUE, cur_buff ); } } else { return_status = SS$_NORMAL; return_status = insque_buffer ( FREE_QUE, cur_buff ); } return return_status; } not_used () /* The network command message is not used in this program. Insert the * * buffer into the FREE_QUE for later use. */ { return insque_buffer ( FREE_QUE, cur_buff ); } establish_link () /* 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. */ { int start, info_cnt, i, j, unit, func; i = cur_buff->msg_area.mbx_msg.name_info[0] + 1; info_cnt = cur_buff->msg_area.mbx_msg.name_info[i]; start = i + 1; for ( i = 0; i < info_cnt; i++ ) ncb[i] = cur_buff->msg_area.mbx_msg.name_info[start+i]; ncb_desc.dsc$w_length = info_cnt; return_status = allocate_lct ( &index ); if ( return_status & STS$M_SUCCESS ){ return_status = sys$assign ( &net_device, &lct[index].channel, 0, &netcmd_mbx ); if ( return_status & STS$M_SUCCESS ) { getdvi_itm.ret_info = &unit; return_status = sys$getdviw ( 0, lct[index].channel, 0, &getdvi_itm, &iosb, 0, 0, 0 ); if ( return_status & STS$M_SUCCESS ) { return_status = iosb.status; if ( return_status & STS$M_SUCCESS ) { lct[index].unit = unit; return_status = sys$qiow ( 0, lct[index].channel, IO$_ACCESS, &iosb, 0, 0, 0, &ncb_desc, 0, 0, 0, 0 ); if ( return_status & STS$M_SUCCESS ) { if ( iosb.status & STS$M_SUCCESS ) { lct[index].cur_buff = cur_buff; return_status = issue_link_read ( index ); } else { return_status = check_fatal_accept ( iosb.status ); if ( return_status & STS$M_SUCCESS ) return_status = link_failure (lct[index].unit); } } } } } } else { func = IO$_ACCESS | IO$M_ABORT; return_status = sys$qiow ( 0, netdcl_chan, func, &iosb, 0, 0, 0, &ncb_desc, 0, 0, 0, 0 ); if ( return_status & STS$M_SUCCESS ) return_status = insque_buffer ( FREE_QUE, cur_buff ); } return return_status; } shutdown () /* A NETSHUT message was received in the network command mailbox. Set a * * flag to drop through the main processing loop and terminate. The * * buffer is inserted into the FREE_QUE for general housekeeping purposes * * only. */ { net_shut = 1; return insque_buffer ( FREE_QUE, cur_buff ); } issue_netcmd_read () /* Issue a read on the network command mailbox. The astid will inform * * the AST routine which buffer completed. */ { netcmd_buff->cmplt_stat.astid=NET_CMD; return sys$qio ( 0, mbx_chan, IO$_READVBLK, &netcmd_buff->iosb, &ast_routine, NET_CMD, &netcmd_buff->msg_area.mbx_msg, mbx_msg_len, 0, 0, 0, 0 ) ; } issue_link_read ( index ) /* Issue an asynchronous read on a logical link. The astid will inform * * the AST routine which buffer completed. */ char index ; { cur_buff->cmplt_stat.ast.ndx = index; cur_buff->cmplt_stat.ast.type = NET_RD; return sys$qio ( 0, lct[index].channel, IO$_READVBLK, &cur_buff->iosb, &ast_routine, cur_buff->cmplt_stat.astid, &cur_buff->msg_area.data, sizeof(cur_buff->msg_area.data), 0, 0, 0, 0 ); } issue_link_write ( index ) /* Issue an asynchronous write on a logical link. The astid will inform * * the AST routine which buffer completed. */ char index ; { cur_buff->cmplt_stat.ast.ndx = index; cur_buff->cmplt_stat.ast.type = NET_WRT; return sys$qio ( 0, lct[index].channel, IO$_WRITEVBLK, &cur_buff->iosb, &ast_routine, cur_buff->cmplt_stat.astid, &cur_buff->msg_area.data, sizeof (cur_buff->msg_area.data), 0, 0, 0, 0 ); } read_database () /* Read the database using the name field of the request buffer. The * * status of the read request is placed in the status field of the buffer * * for return to the requestor. */ { db_rab.rab$l_kbf = &cur_buff->msg_area.data.name; db_rab.rab$l_ubf = &cur_buff->msg_area.data; return_status = sys$get ( &db_rab ); cur_buff->msg_area.data.status = return_status; if ( return_status == RMS$_RNF ) return_status = SS$_NORMAL; return return_status; } find_lct ( unit, pindex ) /* Find a link control table entry based on the unit. */ short int unit; char *pindex; { int found, i; return_status = SS$_NORMAL; found = 0; for ( i = 0; i < MAX_LINKS && !found; i++ ) { if ( lct[i].unit == unit ) found = 1; } if ( !found ) return_status = LIB$_NOTFOU; else *pindex = i-1; return return_status; } release_lct ( index ) /* 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. */ char index; { lct_alloc_mask ^= ( 1 << index ); lct[index].unit = 0; lct[index].channel = 0; lct[index].cur_buff = 0; return SS$_NORMAL; } allocate_lct ( pindex ) /* Allocate the first available link control table (lct) entry. Then * * set the corresponding bit in the allocation mask. */ char *pindex; { return_status = lib$ffc ( &start_pos, &mask_siz, &lct_alloc_mask, pindex ); if ( return_status & STS$M_SUCCESS ) lct_alloc_mask |= ( 1 << *pindex ); return return_status; } remque_buffer ( queue, cur_buff ) /* Insert the buffer into the specified queue, FREE_QUE or LIVE_QUE. * * Relative queues are used because of the interlock supplied in the RTL * * routine. Virtual address queues could be used, but AST disabling * * would be necessary to eliminate queue skews. */ int cur_buff; char queue; { return lib$remqhi ( &que_hdr[queue].flink, cur_buff, &rtry ); } insque_buffer ( queue, cur_buff ) /* Insert a buffer into the specified queue, FREE_QUE or LIVE_QUE. */ int *cur_buff; char queue; { return lib$insqti ( cur_buff, &que_hdr[queue].flink, &rtry ); } 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; } ast_routine ( ast_param ) /* AST routine. This module handles all 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 for processing in the main * * module. NET_RD AND NET_WRT QIOs work in a pair serially and are * * reissued in the main processing module. NET_CMD QIOs are reissued * * immediately to accomodate multiple network requests. */ struct ast_blk ast_param; { int ast_ret_status; switch ( ast_param.type ) { case NET_RD : ast_ret_status = insque_buffer ( LIVE_QUE, lct[ast_param.ndx].cur_buff ); break; case NET_WRT : ast_ret_status = insque_buffer ( LIVE_QUE, lct[ast_param.ndx].cur_buff ); break; case NET_CMD : ast_ret_status = que_and_reissue (); break; default : ast_ret_status = SS$_BADPARAM; break; } if (ast_ret_status & STS$M_SUCCESS) { ast_ret_status = sys$wake ( 0, 0 ); if ( ast_ret_status != SS$_NORMAL ) exit ( ast_ret_status ); } else exit ( ast_ret_status ); } que_and_reissue () { int ret_status; ret_status = insque_buffer ( LIVE_QUE, netcmd_buff ); if ( ret_status & STS$M_SUCCESS ) { ret_status = remque_buffer ( FREE_QUE, &netcmd_buff ); if ( ret_status & STS$M_SUCCESS ) ret_status = issue_netcmd_read (); } return ret_status; }