/*==================================================================== ** ** ABSTRACT: (TELNETSYM.C V1.3) ** ** This program is an example of how to customize the VMS supplied ** print symbiont to support printing over TCP/IP networks to a device ** such as a telnet terminal server. It's function is similar to the ** LATSYM symbiont for LAT. ** ** This symbiont can be used to drive a printer connected to a port ** on a DECserver 300 when the telnet listener options are enabled. ** At present the symbiont does not negotiate TELNET options and will ** simply ignore any option requests sent to it. (This does not appear ** to be a problem for the DECserver 300) This symbiont has been ** used with other terminal servers such as the CISCO STX10 and found ** to function properly. ** ** The symbiont was designed for unattended operation in a wide area ** network. When a print device becomes unreachable the symbiont will ** periodically poll waiting for the device to become reachable, and ** resume printing any queued jobs. If a device becomes unreachable ** during the printing of a job, the remainder of the job's output ** is discarded. ** ** This symbiont is multithreaded and can accommodate up to 16 concurrent ** threads. Each thread corresponds to a print queue. The symbiont is ** fired up when the 1st queue is started using this symbiont as a ** processor. ** ** Two functions of the VMS symbiont are replaced: ** ** JOB SETUP - used to open a socket to the TCP/IP port. Unsuccessful ** open attempts are retried via a TIMER AST and the PSM_PENDING option. ** ** OUTPUT - Sends the data to the socket. ** ** Using the Print Symbiont ** ** An example of how to utilize this symbiont is given below. ** ** Build the symbiont and copy to sys$system: ** ** $ CC TELNETSYM ** $ LINK TELNETSYM, SYS$SHARE:UCX$IPC/LIB, SYS$SHARE:VAXCRTL/LIB ** $ COPY TELNETSYM.EXE SYS$SYSTEM: ** ** To start a queue using the symbiont do the following: ** ** $ INIT/QUE/PROCESS=TELNETSYM/START/ON="server_ip_nodename:port_number" ** ** The value of "server_ip_nodename:port_number" for a printer connected to ** a DECserver 300 with an IP nodename of SERVER listening on ** port 2007 would be: "SERVER:2007" ** ** ** ENVIRONMENT: ** UCX V1.2 or higher, VMS V5.2 or higher ** ** AUTHOR: ** Kris Anderson - Digital STO DTN 445-6552 ** ** CREATION DATE: Dec 1, 1991 ** ** MODIFICATION HISTORY: ** ** 03-APR-1992 KAA Handle "stop/que/...." cases properly ** ** 26-MAY-1992 KAA Support PSM$K_WRITE_NOFORMAT for print/passall ** **============================================================================= */ /* INCLUDE FILES */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* change hostent to comply with BSD 4.3*/ #include #include /* INET symbol definitions */ #include /* A header file does not exist for the PSM$ constants, so these are declared here. See SYS$LIBRARY:LIB.MLB ($PSMDEF) for the complete MACRO definition of these constants. */ #define PSM$K_INPUT 5 #define PSM$K_OUTPUT 7 #define PSM$K_OPEN 4 #define PSM$K_JOB_SETUP 8 #define PSM$K_JOB_COMPLETION 22 #define PSM$K_WRITE 9 #define PSM$K_WRITE_NOFORMAT 10 #define PSM$K_START_TASK 16 #define PSM$K_CLOSE 2 #define FAILURE -1 /* Miscellaneous constants */ #define DELAY_EF 1 /* event flag */ #define MAX_SYMBIONT_STREAMS 16 /* number of queues supported by 1 symbiont process */ #define PACKET_SIZE 256 /* largest packet written to socket */ /* Miscellaneous MACROs */ #define DISABLE_ASTS(status) status = sys$setast(0) #define ENABLE_ASTS(status) { if (status == SS$_WASSET ) sys$setast(1); } /* These VMS condition codes are pulled in externaly at link time */ //extern int PSM$_PENDING; //extern int PSM$_EOF; //extern int PSM$_FUNNOTSUP; /* Messages are logged to this file. */ static FILE logfile; /* Each program thread has one of these structures. */ typedef struct work_area_struct { int chan; /* IO channel to socket */ char read_check_passed; /* flag */ char stream_open; /* flag */ int retry_delta[2]; /* VMS binary time */ int read_timeout[2]; /* VMS binary time */ int link_idle_timeout[2]; /* VMS binary time */ char socket_address[80]; } work_area_type; typedef struct request_packet_struct { work_area_type *work_area; int request_id; short iosb[4]; char read_buffer[256]; } request_packet_type; void log_error (char *,int); int cleanup (work_area_type *work_area); int open_socket (work_area_type *work_area); int output_routine ( int *request_id, struct work_area_struct *work_area, int *f_code, struct dsc$descriptor *funcdesc, int *funcarg ); int job_setup_routine ( int *request_id, struct work_area_struct *work_area, int *f_code, struct dsc$descriptor *funcdesc, int *funcarg ); typedef int (*func) ( int *, struct work_area_struct *, int *, struct dsc$descriptor *, int *); extern "C" { int psm$replace( int *, func); int psm$print ( int *,int *,int * ); int psm$report ( int *,int); } main () { int status; int code; int privs; int streams = MAX_SYMBIONT_STREAMS; /* number of concurrent threads */ int bufsiz = PACKET_SIZE; /* size of one network packet */ int worksiz = sizeof ( struct work_area_struct ); int job_completion_routine(); /* When VMS starts the symbiont only SETPRV is turned on. We need NETMBX & TMPMBX to do the TCP/IP stuff. */ privs = PRV$M_NETMBX | PRV$M_TMPMBX; status = sys$setprv ( 1, &privs, 0, 0); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); code = PSM$K_JOB_SETUP; status = psm$replace( &code, job_setup_routine ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); code = PSM$K_OUTPUT; status = psm$replace( &code, output_routine ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); /* Pass control to the VMS print symbiont. Our routines will be called when needed. */ status = psm$print ( &streams, &bufsiz, &worksiz ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); } /* **++ ** ROUTINE: ** ** int job_setup_routine ( ** int *request_id, ** struct work_area_struct *work_area, ** int *f_code, ** struct dsc$descriptor *funcdesc, ** int *funcarg ) ** ** ** FUNCTIONAL DESCRIPTION: ** ** This routine initializes the work area and prepares to start ** printing a job by opening a socket to the printer port. ** ** ** The VMS print symbiont calls this routine 3 separate times prior ** to printing a job. It is called with the following function codes: ** ** 1) PSM$K_OPEN ** 2) PSM$K_INPUT ** 3) PSM$K_CLOSE ** ** The routine triggers on the open code to open the socket to the ** tcp/ip port. ** ** ** FORMAL PARAMETERS: ** ** See the VMS print symbiont documenation ** ** RETURN VALUE: ** ** VMS condition code ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** ** ** **-- */ int job_setup_routine ( int *request_id, struct work_area_struct *work_area, int *f_code, struct dsc$descriptor *funcdesc, int *funcarg ) { int retval; int status; int ast_status; request_packet_type *request_packet; int open_socket_with_retry ( request_packet_type *request_packet ); switch ( *f_code ) { case PSM$K_OPEN: retval = PSM$_PENDING; status = sys$cantim ( work_area, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); request_packet = (request_packet_type *) malloc ( sizeof(*request_packet) ); if ( ! request_packet ) return ( SS$_INSFMEM ); /* initialize the request packet and work area */ request_packet->work_area = work_area; request_packet->request_id = *request_id; if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); status = sys$dclast ( open_socket_with_retry, request_packet, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) retval = status; break; case PSM$K_CLOSE: retval = SS$_NORMAL; break; default: retval = PSM$_FUNNOTSUP; } return ( retval ); } /* **++ ** ROUTINE: ** ** int job_output_routine ( ** int *request_id, ** struct work_area_struct *work_area, ** int *f_code, ** struct dsc$descriptor *funcdesc, ** int *funcarg ) ** ** ** FUNCTIONAL DESCRIPTION: ** ** This routine is called once when the queue is started with ** a PSM$K_OPEN code, then multiple times with a PSM$K_OUTPUT code ** when data is to be sent to a socket. ** ** When called with the open code, the routine verifies the ** address port specification is in the following format: ** ** "x.x.x.x:yyyy" where: x is an octet of an IP address ** and y is the port to send output to. ** ** e.g. "192.67.209.100:2003" ** ** This specification comes from the /ON= qualifier on the ** START/QUEUE DCL command. ** ** When called with the PSM$K_OUTPUT code, data is simply written ** to the output socket. ** ** ** FORMAL PARAMETERS: ** ** See the VMS print symbiont documenation ** ** RETURN VALUE: ** ** VMS condition code ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ int output_routine ( int *request_id, struct work_area_struct *work_area, int *f_code, struct dsc$descriptor *funcdesc, int *funcarg ) { request_packet_type *request_packet; int retval; int status; int ast_status; char msg[132]; char host_str[132]; char port_str[132]; void shutdown_link_ast ( work_area_type *); int output_completion_ast (request_packet_type *request_packet ); $DESCRIPTOR(retry_delta_asc, "0 00:03:00.00"); $DESCRIPTOR(link_idle_timeout_asc, "0 00:02:00.00"); $DESCRIPTOR(read_timeout_asc, "0 00:00:05.00"); retval = SS$_NORMAL; switch ( *f_code ) { /* The PSM$K_OPEN case receives the value of the /on qualifier. This value is parsed and the contents are verified. */ case PSM$K_OPEN: memcpy ( work_area->socket_address, funcdesc->dsc$a_pointer, funcdesc->dsc$w_length ); work_area->socket_address[ funcdesc->dsc$w_length ] = '\0'; work_area->stream_open = 1; work_area->chan = -1; /* Check the validity of the address */ if ( sscanf ( work_area->socket_address, "\"%[^:]:%[^\"]", host_str, port_str) < 2 ) return ( SS$_IVDEVNAM ); if ( gethostbyname (host_str) == NULL) return ( SS$_IVDEVNAM ); /* verify the number is a valid TCP/IP port number */ if ( atoi ( port_str ) < 0 || atoi ( port_str ) > 16000 ) return ( SS$_IVDEVNAM ); status = sys$bintim (&retry_delta_asc, work_area->retry_delta ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); status = sys$bintim (&read_timeout_asc, work_area->read_timeout ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); status = sys$bintim (&link_idle_timeout_asc, work_area->link_idle_timeout); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); break; case PSM$K_WRITE: case PSM$K_WRITE_NOFORMAT: /* Disable AST's while the output request is being queued */ DISABLE_ASTS(ast_status); retval = PSM$_PENDING; /* verify we have a channel open */ if ( work_area->chan < 0 ) retval = SS$_NOIOCHAN; else /* if no data to write simply return successful status */ if ( funcdesc->dsc$w_length == 0 ) retval = SS$_NORMAL; else { /* Setup to send data to the socket */ request_packet = (request_packet_type *) malloc ( sizeof (*request_packet) ); if ( ! request_packet ) { ENABLE_ASTS(ast_status); return ( SS$_INSFMEM ); } request_packet->request_id = *request_id; request_packet->work_area = work_area; status = sys$qio(0, /* Event flag */ work_area->chan, /* Channel number */ IO$_WRITEVBLK, /* I/O function */ request_packet->iosb, /* I/O status block */ output_completion_ast, /* AST routine */ request_packet, /* AST parameter */ funcdesc->dsc$a_pointer,/* P1 buffer */ funcdesc->dsc$w_length, /* P2 buffer length */ 0,0,0,0) ; if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error ("send", status); cleanup ( work_area ); free ( request_packet ); retval = status; } status = sys$cantim ( work_area, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); } /* Restore AST state */ ENABLE_ASTS(ast_status); break; case PSM$K_CLOSE: if ( work_area->chan > 0 ) cleanup ( work_area ); work_area->stream_open = 0; retval = SS$_NORMAL; break; /* Return the "function not supported" code for all unrecognized function codes. */ default: retval = PSM$_FUNNOTSUP; break; } return ( retval ); } /* **++ ** ** ROUTINE: ** ** void shutdown_link_ast( work_area_type *work_area ) ** ** FUNCTIONAL DESCRIPTION: ** ** This AST routine is called as the result of the "link" being ** inactive for a period of time. This routine terminates the link. ** ** FORMAL PARAMETERS: ** ** [INPUT] work_area pointer to the work area for the link. ** ** RETURN VALUE: ** ** N/A (AST routine) ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ void shutdown_link_ast( work_area_type *work_area ) { int status; status = sys$cantim ( work_area, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); if (work_area->chan > 0) { cleanup( work_area ); } } /* **++ ** ROUTINE ** ** open_socket_with_retry ( ** struct work_area_struct *work_area ) ** ** FUNCTIONAL DESCRIPTION: ** ** This routine executes at AST level and opens a socket to the ** destination IP address/socket. If the open attempt is unsuccesful ** then a TIMER is set to retry later. When retry is required ** PSM$_PENDING is returned. ** ** FORMAL PARAMETERS: ** ** [MODIFY] request_packet->work_area->chan ** ** Should be less than zero to indicate the channel NOT open, ** when called the first time. ** ** [INPUT] request_packet->request_id ** ** used as the request_id to PSM$REPORT when work_area->report_status ** is non-zero. ** ** [INPUT] request_packet->open_request_in_progress ** ** A flag that is set when an open request has not yet been ** satisfied. ** ** RETURN VALUE: ** ** This routine executes as an AST routine, return value N/A ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ int open_socket_with_retry ( request_packet_type *request_packet ) { int status; char try_again = 0; /* flag */ work_area_type *work_area = request_packet->work_area; int read_completion_ast ( request_packet_type *request_packet ); int read_timeout_ast ( work_area_type *work_area ); /* Check for spurious timer interrupt */ if ( ! work_area->stream_open ) { free( request_packet ); return ( SS$_NORMAL ); } /* If a connection does not exists make an attempt to establish a connection */ if ( work_area->chan < 0 ) { work_area->chan = open_socket ( work_area ); if ( work_area->chan < 0 ) try_again = 1; } /* if a connection does exists make sure the read check has passed. The DECserver 300 will accept a connection even if it is busy. The next read or write will disconnect the link. Therefore test for this condition by issuing a read. Using this technique is not as clean as I would like, but unfortunately only reading or writing to the socket will reveal whether a true connection has been established. */ if ( work_area->chan > 0 ) if ( work_area->read_check_passed ) { status = psm$report ( &request_packet->request_id,status ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); free ( request_packet ); } else { status = sys$qio ( 0, /* Event flag */ work_area->chan, /* Channel number */ IO$_READVBLK, /* I/O function */ request_packet->iosb, /* I/O status block */ read_completion_ast, request_packet, request_packet->read_buffer, /* P1 buffer */ sizeof( *request_packet->read_buffer ), /* P2 buffer length */ 0,0,0,0) ; if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error( "read", status ); cleanup ( work_area ); try_again = 1; } else { /* The read will hang for the CISCO STSX10 since no data is transmitted by this device. Therefore a timeout on the read is implemented. */ status = sys$setimr ( 0, work_area->read_timeout, read_timeout_ast, work_area, 0); if ( ! $VMS_STATUS_SUCCESS (status) ) lib$stop ( status ); } } /* If unsuccessful then set a timer and retry later. */ if ( try_again ) { status = sys$setimr ( 0, work_area->retry_delta, open_socket_with_retry, request_packet, 0); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); } } /* **++ ** ** ROUTINE: ** ** int read_timeout_ast ( work_area_type *work_area ) ** ** FUNCTIONAL DESCRIPTION: ** ** This AST routine is called should the read check timeout. ** SYS$CANCEL is used to force the read to terminate. ** ** FORMAL PARAMETERS: ** ** [INPUT] work_area pointer to the work area for the link. ** ** RETURN VALUE: ** ** N/A (AST routine) ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ read_timeout_ast ( work_area_type *work_area ) { int status; if ( work_area->chan > 0 ) { status = sys$cancel ( work_area->chan ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); } } /* **++ ** ** ROUTINE: ** ** int read_completion_ast ( request_packet_type *request_packet ) ** ** FUNCTIONAL DESCRIPTION: ** ** This function is called when the "read check" IO completes. If ** the IO status block indicates that the read was cancelled this is ** not considered an error, since this condition occurs when a device ** does not send any data. ** ** FORMAL PARAMETERS: ** ** [OUTPUT] request_packet->work_area->read_check_passed ** set to true if the check passed ** ** ** RETURN VALUE: ** ** N/A (AST routine) ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ read_completion_ast ( request_packet_type *request_packet ) { int status; work_area_type *work_area = request_packet->work_area; status = sys$cantim( work_area, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); status = request_packet->iosb[0]; /* If an error occurred set a timer interrupt to try again later. Abort and Cancel codes are not treated as errors. They are the result of canceling IO on the channel when the read timed out. */ if ( ! $VMS_STATUS_SUCCESS (status) && status != SS$_ABORT && status != SS$_CANCEL ) { log_error( "read", status ); if ( work_area->chan > 0 ) cleanup ( work_area ); status = sys$setimr ( 0, work_area->retry_delta, open_socket_with_retry, request_packet, 0); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); } else { work_area->read_check_passed = 1; status = psm$report ( &request_packet->request_id,status ); free ( request_packet ); } } /* **++ ** ** ROUTINE: ** ** int output_completion_ast ( ** request_packet_type *request_packet ) ** ** FUNCTIONAL DESCRIPTION: ** ** This AST routine is called after a block of data has been written to ** a socket. ** ** FORMAL PARAMETERS: ** ** [INPUT] request_packet->iosb VMS condition code indicating ** success or failure ** ** [INPUT] request_packet->chan IO channel of the socket ** ** [INPUT] request_packet->request_id The symbiont request id for ** the output function. ** ** RETURN VALUE: ** ** N/A (AST routine) ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ int output_completion_ast ( request_packet_type *request_packet ) { int status; void shutdown_link_ast( work_area_type * ); work_area_type *work_area = request_packet->work_area; status = sys$cantim ( work_area, 0 ); if ( ! $VMS_STATUS_SUCCESS(status) ) lib$stop ( status ); if ( ! $VMS_STATUS_SUCCESS(request_packet->iosb[0]) ) { log_error ( "output", (int) request_packet->iosb[0] ); /* If the channel is still assigned then cleanup the link. */ if ( work_area->chan > 0 ) cleanup ( work_area ); } else { /* Set a timer to disconnect the link if idle for a long period of time. */ sys$setimr ( 0, work_area->link_idle_timeout, shutdown_link_ast, work_area, 0); } status = psm$report ( &request_packet->request_id,status ); if ( ! $VMS_STATUS_SUCCESS (status) ) lib$stop ( status ); /* NOTE: The request_packet is allocated by the send_data_to_socket routine. */ free ( request_packet ); return ( status ); /* not truly necessary for an AST routine */ } /* **++ ** ** ROUTINE: ** ** int open_socket (char *socket_address ) ** ** FUNCTIONAL DESCRIPTION: ** ** This routine attempts to open a socket to the print device. The ** code is a modified version of the UCX example code, ** "UCX$TCP_SERVER_IPC.C". ** ** FORMAL PARAMETERS: ** ** [INPUT] char *socket_address a character string which specifies ** the destination IP address and ** port. Format is as follows: ** ** "ip_node:tcp_port" ** ** ** RETURN VALUE: ** ** -1 if an error occured ** if no error then the channel to the socket ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ int open_socket (work_area_type *work_area ) { /* Structures used to pass parameters to QIO calls */ struct socket_descriptor_struct { int length ; struct sockaddr *sock ; }; short channel ; /* INET channel */ short sck_parm[2] ; /* Socket creation parameter */ char data[512]; char *socket_address = work_area->socket_address; struct sockaddr_in remote_host; struct socket_descriptor_struct socket_desc = { sizeof ( remote_host ), (struct sockaddr *) &remote_host }; char remote_addr[32]; int remote_addr_len; struct hostent hostentstruct; /* Storage for hostent data. */ struct hostent *hostentptr; /* Pointer to hostent data. */ short iosb [4]; /* I/O status block */ int status; char host_str[128]; char port_str[128]; char msg[128]; struct dsc$descriptor dev = { 3, DSC$K_CLASS_S, DSC$K_DTYPE_T, "BG:"} ; /* INET device */ /* Parse the host and port str from the input string. NOTE: the quotation marks are expected to be in the string */ sscanf (socket_address, "\"%[^:]:%[^\"]", host_str, port_str); sprintf ( msg, "Open %s", socket_address ); /* * Initialize the parameters. */ sck_parm[0] = INET$C_TCP ; /* TCP/IP protocol */ sck_parm[1] = INET_PROTYP$C_STREAM ; /* stream type of socket */ /* * Get pointer to network data structure for socket 2 (remote host). */ if ((hostentptr = gethostbyname (host_str)) == NULL) { log_error( "gethostbyname",0); return ( FAILURE ); } /* * Copy hostent data to safe storage. */ hostentstruct = *hostentptr; /* * Fill in the name & address structure for socket 2. */ remote_host.sin_family = hostentstruct.h_addrtype; remote_host.sin_port = htons( atoi ( port_str ) ); remote_host.sin_addr = * ((struct in_addr *) hostentstruct.h_addr); /* * Assign a channel to INET device. */ status = sys$assign( &dev, &channel, 0, 0) ; if ( ! $VMS_STATUS_SUCCESS(status) ) { log_error( msg, status ); return ( FAILURE ); } work_area->chan = channel; /* * Create and bind the device socket to local host. */ status = sys$qiow( 0, /* Event flag */ channel, /* Channel number */ IO$_SETMODE, /* I/O function */ iosb, /* I/O status block */ 0, 0, &sck_parm, /* P1 Socket creation parameter */ 0x01000000 | SOCKOPT$M_REUSEADDR, /*P2 - by value with command bit=set */ 0, /* P3 Socket bind parameter */ 0,0,0) ; if ( $VMS_STATUS_SUCCESS (status) ) status = iosb[0]; if ( ! $VMS_STATUS_SUCCESS(status) ) { log_error( msg, status ); cleanup ( work_area ); return ( FAILURE ); } /* * Define the peer socket name and address-Connect. */ status = sys$qiow( 0, /* Event flag */ channel, /* Channel number */ IO$_ACCESS, /* I/O function */ iosb, /* I/O status block */ 0,0, 0,0, &socket_desc, /* P3 Remote IP address*/ 0,0,0); if ( $VMS_STATUS_SUCCESS (status) ) status = iosb[0]; if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error( msg, status ); /* msg is setup at begining of routine */ cleanup ( work_area ); return ( FAILURE ); } return ( channel ); } /* **++ ** ** ROUTINE: ** ** cleanup ( work_area_type *work_area ) ** ** FUNCTIONAL DESCRIPTION: ** ** This routine closes a socket by cancelling all IO on the socket ** channel and deassigning the channel. ** ** FORMAL PARAMETERS: ** ** [MODIFY] work_area->channel IO channel of socket ** ** RETURN VALUE: ** ** None ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ int cleanup ( work_area_type *work_area ) { int status; int channel = work_area->chan; short iosb [4] ; /* I/O status block */ /* Cancel all IO currently on the channel first, such as the asynchronous read. */ status = sys$cancel ( channel ); if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error ( "cancel", status ); } /* * Close the socket. */ status = sys$qiow( 0, channel, IO$_DEACCESS, iosb, 0,0,0,0,0,0,0,0) ; if ( $VMS_STATUS_SUCCESS (status) ) status = iosb[0]; if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error ( "deaccess", status ); } /* Finished with the channel, so free it. */ status = sys$dassgn (channel); if ( ! $VMS_STATUS_SUCCESS (status) ) { log_error ( "deassign", status ); } work_area->chan = -1; work_area->read_check_passed = 0; return ( SS$_NORMAL ); } /* **++ ** ** ROUTINE: ** ** void log_message ( char *msg ) ** ** FUNCTIONAL DESCRIPTION: ** ** This routine logs a message to the file defined by the logical ** TELNETSYM_LOGFILE. If the logical is not defined then ** the message is logged to SYS$SYSTEM:TELNETSYM.LOG. The current ** date and time is prefixed to all messages. ** ** FORMAL PARAMETERS: ** ** [INPUT] char *msg message to write to logfile ** ** RETURN VALUE: ** ** None ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ void log_message ( char *msg ) { FILE *logfile; unsigned long current_time; char *current_time_str; char *log_file_name; int time_len; if ( ! (log_file_name = getenv ("TELNETSYM_LOGFILE")) ) log_file_name = "TELNETSYM.LOG"; logfile = fopen ( log_file_name, "a" ); if ( ! logfile ) { lib$stop ( vaxc$errno ); } time ( ¤t_time ); current_time_str = ctime ( ¤t_time ); time_len = strlen ( current_time_str ); fprintf (logfile, "%.*s, %s\n", time_len - 1, current_time_str, msg); fclose (logfile); } /* **++ ** ** ROUTINE: ** ** int log_error ( char *prefix, ** [int vms_status] ) ** ** FUNCTIONAL DESCRIPTION: ** ** This routine is similar to perror(), except its output is ** sent to the logging file. ** ** FORMAL PARAMETERS: ** ** ** [INPUT] char *prefix pointer to a character string which will ** be printed prior to printing an error ** message. ** ** [INPUT] int vms_status [optional] VMS condition code to use ** for error text retrieval ** ** RETURN VALUE: ** ** None ** ** SIDE EFFECTS: ** ** None ** ** DESIGN: ** ** None ** **-- */ void log_error ( char *prefix, int vms_status ) { char msg[256]; sprintf (msg, "%s: %s", prefix, strerror ( EVMSERR, vms_status ) ); log_message ( msg ); }