Summary: Printing from Sun to VAX Printer

From: Sue Chichester (SUE%geneseo.bitnet@eecs.nwu.edu)
Date: Mon Feb 11 1991 - 08:03:00 CST


On Feb 6, 8:13pm, Sue Chichester wrote:
} Subject: Printing from Sun to VAX printer
} I have a bunch of suns connected to my campus backbone. I'd like to
} be able to print from the suns to a LAT printer connected to my VAX/VMS
} machine that is also connected to the backbone.
}
} I saw an article in sun-spots about dnaprint.shar but it said you need
} sunlink DNI product. I'm not sure what this even is.
}
} Does anyone have a way of doing this?
}
} P.S. I'm running SunOS 4.1.1.
}
} Thanks,
} Sue Chichester
} SUNY Geneseo
} sue@geneseo.bitnet
}-- End of excerpt from Sue Chichester
 
Thanks to everyone who told me that sunlink DNI is decnet for the suns.
 
>From Dave Bianchi: Thanks!!!
 
This is what I chose to do. It took 5 minutes to set up and worked the first
time (a definite plus in my book). As it turns out if you are running
Multinet on your VAX, Multinet provides an LPD server. It takes about
two minutes to configure and they show you exactly how to do it in the
Multinet System Administrators Guide. It was just a matter of telling the
server which ip hosts it will accept. You have to setup a username to
be used (kinda as a dummy) for those who are printing from remote machines
and don't have a matching username on both machines.
Then it's just a matter of putting in a printcap entry for the printer. And
an example is in the Multinet manual. Works really great and very easy to
setup!!!
 
 
>From Brian White: I have an Ultrix machine and this would have worked for
                     me as well.
All our Suns spool their printing through an Ultrix (DEC's version of BSD UNIX)
machine. Version 4.0 and above of Ultrix support Printserver (that which maketh
the LPS series of printers fly) client software (it's included); you still need
a VMS machine to boot the printer.
 
 
>From Doug Neuhauser:
 
Doug told me it was possible if I had a Xyplex terminal server. I do, and
it does support both LAT and TCP/IP, so I assume I could have done it this
way too. I was planning on this until I got the note from Dave Bianchi and
took a peak in my Multinet manual and saw how simple it was. Doug has
sent some great details here that I'm included in my summary.
 
I have a 1500 (remote boot) and 1800 (with embedded boot floppy), both of
which run LAT and TCP/IP. Along with the distribution tape comes the
following programs: The first (xyp_tcp_spff.c) is used for BSD lpd filter
-- the second is used for Sys V (I believe). This may NOT be exactly what
is on the tape -- I have floppy distribution for my 1800 (which did not
contain the programs) and had to scan the code in from printed form in the
manual and "fix it up".
 
Note that this will ONLY work if your Xyplex terminal server supports BOTH
LAT and TCP/IP. I believe that the older models did NOT support both -- you
may have to upgrade either software and/or hardware to get TCP/IP support.
Contact your Xyplex salesperson if you have questions.
 
The ":xn=IP_ADDRESS:" entry specifies the IP address of the terminal server,
and the ":xp=port_#:" specifies the IP port number of the specific line on
the terminal server. The program xyp_tcp_spff.c actually goes back and
reads the printcap entry to extract this info.
 
Files:
1. printcap entry
2. psint.sh - modified Transcript shell script that is used as the
various input filters for the printcap entry. All of the ps?? input filters
mentioned in the printcap entry are really hard links to this file
(presumablty symlinks would work also).
3. xyp_tcp_spff.c - BSD lpd filter.
4. xyp_filter.c - test program or SysV filter (??)
 
My printcap entry is as follows: It is a modification of the TranScript
filter(s) where I have replaced the line that invokes "pscomm" with a line
that invokes the xyplex filter instead. All of the ps?? filters mentioned
are really the same file -- all hard links to this file.
--------------------------------> cut here <--------------------------------
#
# PostScript printer driven by xyplex filter.
#
# lwprint Laserwriter in South Mudd, via xyplex terminal server
# The xyplex programs are set up to be a replacement for the TranScript's pscomm
 
# so the following setup has duplicated the transcript stuff, replacing
# pscomm with xyp_filt in the command files.
#
lwprint|ps|postscript|PostScript:\
        :lp=/dev/lwprint:sd=/usr/spool/lwprint:\
        :lf=/usr/adm/printers/lwprint.log:af=/usr/adm/printers/lwprint.acct:\
        :sf:sb:mx#0:\
        :xn=131.215.67.161:xp=3400:\
        :if=/usr/local/lib/xyplex/psif:\
        :of=/usr/local/lib/xyplex/psof:gf=/usr/local/lib/xyplex/psgf:\
        :nf=/usr/local/lib/xyplex/psnf:tf=/usr/local/lib/xyplex/pstf:\
        :rf=/usr/local/lib/xyplex/psrf:vf=/usr/local/lib/xyplex/psvf:\
        :cf=/usr/local/lib/xyplex/pscf:df=/usr/local/lib/xyplex/psdf:
--------------------------------> cut here <--------------------------------
#!/bin/sh
# 4.2BSD line printer spooler interface for PostScript/TranScript printer
# this is the printcap/lpd-invoked top level filter program for ALL file types
# Copyright (c) 1985,1987 Adobe Systems Incorporated. All Rights Reserved.
# GOVERNMENT END USERS: See Notice file in TranScript library directory
# -- probably /usr/lib/ps/Notice
# RCSID: $Header: psint.proto,v 2.2 87/11/17 16:40:51 byron Rel $
 
PATH=/bin:/usr/bin:/usr/ucb
export PATH
 
# set up initial undefined variable values
width= length= indent= login= host= afile=
prog=$0
cwd=`pwd`
pname=`basename $cwd`
 
# define the default printer-specific and TranScript
# configuration options, these may be overridden by
# real printer-specific options in the .options file
 
PSLIBDIR=/usr/local/lib/ps
REVERSE=
VERBOSELOG=1
BANNERFIRST=1
BANNERLAST=0
BANNERPRO=$PSLIBDIR/banner.pro
PSTEXT=$PSLIBDIR/pstext
#:: There are 3 definitions for PSCOMM:
#:: pscomm: Transcript-supplied for serial line printers.
#:: papif: CAP-supplied for CAP appletalk printers.
#:: xyp_tcp_spff: xyplex filter to talk to LAT service on xyplex.
#:: PSCOMM=$PSLIBDIR/pscomm
PSCOMM=/usr/local/lib/xyplex/xyp_tcp_spff
PSBANNER=$PSLIBDIR/psbanner
#::
export PSLIBDIR VERBOSELOG BANNERFIRST BANNERLAST BANNERPRO REVERSE PSTEXT
 
# load the values from the .options file if present
test -r ./.options && . ./.options
 
# parse the command line arguments, most get ignored
# the -hhost vs. -h host is due to one report of someone doing it wrong.
# you could add or filter out non-standard options here (-p and -f)
 
while test $# != 0
do case "$1" in
        -c) ;;
        -w*) width=$1 ;;
        -l*) length=$1 ;;
        -i*) indent=$1 ;;
        -x*) width=$1 ;;
        -y*) length=$1 ;;
        -n) user=$2 ; shift ;;
        -n*) user=`expr $1 : '-.\(.*\)'` ;;
        -h) host=$2 ; shift ;;
        -h*) host=`expr $1 : '-.\(.*\)'` ;;
        -*) ;;
        *) afile=$1 ;;
        esac
        shift
done
 
PATH=$PSLIBDIR:$PATH
export PATH
 
# now exec the format-specific filter, based on $prog
# if - communications filter [default]
# of - banner filter [execed directly]
# nf - ditroff, tf - troff (CAT), gf - plot
# vf - raster, df - TeX DVI, cf - cifplot, rf - fortran
 
prog=`basename $prog`
comm="$PSCOMM -P $pname -p $prog -n $user -h $host $afile"
 
case $prog in
        psif) exec $comm ;;
        psof) exec $PSBANNER $pname ; exit 0 ;;
        psnf) psdit | $comm ;;
        pstf) pscat | $comm ;;
        psgf) psplot | $comm ;;
        psvf|pscf|psdf|psrf) echo "$prog: filter not available." 1>&2 ;
                        psbad $prog $pname $user $host | $comm ;;
esac
 
--------------------------------> cut here <--------------------------------
/*
 *
 * xyp_tcp_spff.c - Output Filter for Xyplex Shared Printer
 *
 */
 
#include <sys/types.h>
#include <sys/file.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <ctype.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
 
 
/* Declare additional constants */
 
#define ERROR -1
#define TRUE 1
#define FALSE 0
 
/* Declare constants that might be useful in tuning */
 
#define MAXLINE 1000 /* length of the character buffer */
#define CALLBACK_TIME 30 /* time to wait before re-register */
 
/* Define structure for target device Internet address and port number */
 
typedef struct {
        unsigned int internet_address;
        unsigned short port_number;
} target_address;
 
/* Declare global storage */
int MyTCPsocket, CONNsocket;
struct sockaddr_in MyTCPaddr;
 
/*
 *
 * Main procedure
 *
 */
 
main(argc,argv)
    int argc;
    char argv;
{
    int len;
    int block_size;
    int waiting_for_callback;
    char line[MAXLINE];
    target_address target;
 
    extern TCPalarmHandler();
 
/*
 * Search the printcap file for the target Internet address
 * and port number.
 */
 
    if (Find_target_address(&target) == ERROR)
        exit(1);
 
/*
 * Establish a port for the callback connection from the cluster
 * controller.
 */
 
    if (TCPopen() == ERROR)
        exit(1);
 
/*
 * Register with the shared printer port on the cluster controller.
 * The registration fails if the response indicates a configuration
 * error.
 * The registration process continues indefinitely if no response is
 * received at all.
 * Block size is a plug number. If you could get this info from the
 * spooler, block size is the number of 512 byte blocks in the job.
 */
 
    block_size = 10;
    if (Register(&target, MyTCPaddr.sin_port, block_size) < 0)
        goto closeTCP;
 
/*
 * Set up timeout alarm handlers and start callback timer.
 */
 
    signal(SIGPIPE,TCPalarmHandler);
    signal(SIGALRM,TCPalarmHandler);
    alarm(CALLBACK_TIME);
 
/*
 * Wait for connect callback from cluster or the callback timeout.
 * If connect callback does not complete in a reasonable period of time
 * re-register with the cluster controller. The callback might not
 * complete within the specified time if either the cluster controller
 * has crashed and restarted or the list of jobs takes a significant
 * time to print. Re-registration in the former case is most desirable
 * and harmless in the latter case.
 */
 
    waiting_for_callback = TRUE;
    while (waiting_for_callback == TRUE)
    {
        CONNsocket = accept(MyTCPsocket,0,0);
        alarm(0);
        if (CONNsocket > 0 )
        {
            waiting_for_callback = FALSE; /* connection complete */
        }
        else
        {
            if (errno == EINTR) /* timeout ? */
            {
                if (Register(&target, MyTCPaddr.sin_port, block_size) < 0)
                    goto closeTCP;
                alarm(CALLBACK_TIME); /* restart timer */
            }
            else
            {
                perror("accept failed. \n");
                goto closeTCP;
            }
        }
    }
 
/*
 * Write initial carriage return and form feed to printer. This may
 * omitted or modified as necessary. One possibility to consider is to
 * set the printer pitch based on -1 and -w parameters passed to the
 * filter.
 */
 
    /*::
    write(CONNsocket,"\r\f",sizeof("\r\f")-l);
    ::*/
 
/*
 * Main data transfer loop.
 * Data is read from stdin by Getlin and buffered in the character
 * array "line". The array is transmitted when full, new-line is seen,
 * or EOF is returned by getchar.
 */
 
    while ((len = GetLine(line,sizeof(line))) > 0)
        if (write(CONNsocket,line,len) < 0)
        {
            perror("write failed. \n");
            close(CONNsocket);
            goto closeTCP;
        }
 
/*
 * Normal termination and closeout
 */
 
    if (close(CONNsocket) < 0)
    {
        perror("close of CONNsocket failed. \n");
        close(MyTCPsocket);
        exit(0);
    }
 
    if (close(MyTCPsocket) < 0)
        perror("close of MyTCPsocket failed. \n");
    exit(0);
 
/*
 * Abnormal termination and closeout
 */
 
    closeTCP:
        close(MyTCPsocket);
        exit(1);
}
 
/*
 * This routine is defined so that alarm clock siqnals are trapped
 * and do not force process termination.
 */
 
TCPalarmHandler()
{
}
 
/*
 * TCPopen - Routine to establish a port for an incoming TCP connection.
 *
 * First a socket is created, then a free TCP port starting with
 * port number 2000 is found and bound the socket. The port
 * is then activated for an incoming connection by calling
 * listen.
 */
 
TCPopen()
{
    unsigned short local_port;
 
    MyTCPsocket = socket(AF_INET,SOCK_STREAM,0);
    if (MyTCPsocket < 0 ) {
        perror ("Xyplex SPF - unable to establish socket for connect\n");
        return(ERROR);
    }
 
/*
 * Set the circuit keep alive option for the socket
 */
 
    if (setsockopt(MyTCPsocket,SOL_SOCKET,SO_KEEPALIVE,(char *)0,0) < 0) {
        perror ("Xyplex SPF - unable to set socket options\n");
        return(ERROR);
    }
 
    bzero((char *)&MyTCPaddr,sizeof(MyTCPaddr));
 
    MyTCPaddr.sin_family = AF_INET;
    MyTCPaddr.sin_addr.s_addr = INADDR_ANY;
 
    local_port = 2000;
 
    for (;;) {
        MyTCPaddr.sin_port = htons(local_port);
        if (bind(MyTCPsocket,(char *)&MyTCPaddr,sizeof(MyTCPaddr)) >=0)
            break;
        if (errno != EADDRINUSE && errno != EADDRNOTAVAIL) {
            perror ("Xyplex SPF - bind failure\n");
            close(MyTCPsocket);
            return (ERROR);
        }
 
        local_port++;
        if (local_port == 65535) {
            perror("Xyplex SPF - all TCP ports in use\n");
            close(MyTCPsocket);
            return (ERROR);
        }
    }
    if (listen(MyTCPsocket,1) < 0) {
        perror("Xyplex SPF - listen failure\n");
        close(MyTCPsocket);
        return (ERROR);
    }
}
 
/*
 * Register with shared printer
 *
 * Send registration message every two seconds until a responsQ
 * is received If response is ok, registration is complete If
 * response ls other than registrdtion queue full, abort the
 * process If the response is a full queue, keep trying; but don't
 * log any errors
 * The destination UDP port in the cluster controller was
 * arbitrarily chosen to be 1024.
 */
 
Register(target, TCPport, size)
    target_address *target;
    unsigned short TCPport;
    unsigned short size;
{
    extern UDPalarmHandler();
 
/* Define format of registration messages */
 
    typedef struct {
        unsigned char dstprot;
        unsigned short dstport,srcport,block_count;
    } registration_message;
 
    typedef struct {
        unsigned char dstprot;
        unsigned short result;
    } registration_response;
 
#define MSG_OK 0x0000
 
#define MSG_QUEFULL 0xFFFE
 
/* Declare storage for registration messages */
 
    registration_message registration_msg;
    registration_response msg_in;
 
    int retry_cnt;
    int MyUDPsocket;
    int good_response;
    struct sockaddr_in DstUDPaddr;
 
/* Establish UDP socket */
 
    bzero((char *)&DstUDPaddr,sizeof(DstUDPaddr));
    DstUDPaddr.sin_family = AF_INET;
    DstUDPaddr.sin_addr.s_addr = target->internet_address;
    DstUDPaddr.sin_port = htons(173);
 
    MyUDPsocket = socket(AF_INET,SOCK_DGRAM,0);
    if (MyUDPsocket < 0) {
        perror("Xyplex SPF - no socket for registration\n");
        return (ERROR);
    }
 
    if (connect(MyUDPsocket,DstUDPaddr,sizeof(DstUDPaddr)) < 0) {
        close(MyUDPsocket);
        perror("Xyplex SPF - cannot connect to UDP socket\n");
        return(ERROR);
    }
 
/* Build and send the registration message */
 
    registration_msg.dstprot = 1;
    registration_msg.dstport = target->port_number;
    registration_msg.srcport = TCPport;
    registration_msg.block_count = htons(size);
 
    if (write(MyUDPsocket,registration_msg,sizeof(registration_msg)) < 0)
    {
        close(MyUDPsocket);
        perror("Xyplex SPF - registration write failed\n");
        return(ERROR);
    }
 
    good_response = FALSE;
    retry_cnt = 0;
 
/* Setup and start a 2 second timer */
 
    signal(SIGALRM,UDPalarmHandler);
    alarm(2);
    while (good_response == FALSE) {
        if (recv(MyUDPsocket,msg_in,sizeof(msg_in),0) > 0) {
            if (ntohs(msg_in.result) == MSG_OK) { /* if success */
                alarm(0); /* cancel timer */
                good_response = TRUE; /* and return */
            }
            else {
                if (ntohs(msg_in.result) != MSG_QUEFULL) { /* not queue full */
 
                    fprintf(stderr,
                            "Xyplex SPF - conflguration error\n");
                    alarm(0); /* cancel timer */
                    return (ERROR);
                }
                else { /* don't log if queue full */
                    retry_cnt = 0;
                }
            }
        }
        else {
            if (errno == EINTR) { /* if timeout */
                write(MyUDPsocket,registration_msg,sizeof(registration_msg));
                alarm(1); /* restart timer */
                retry_cnt++; /* log every 60 trys */
                if (retry_cnt > 60) {
                    retry_cnt = 0;
                    fprintf(stderr,
                            "Xyplex SPF - unable to register with target device\
n");
                }
            }
            else {
                perror("Xyplex SPF - error on UDP receive\n");
                alarm(0); /* cancel timer */
                return (ERROR);
            }
        }
    }
    close(MyUDPsocket);
}
 
/*
 * This routine is defined so that alarm clock signals are trapped
 * and do not force process termination.
 */
 
UDPalarmHandler()
{
}
 
/*
 * GetLine
 *
 * This routine gets data from the standard input stream and fills
 * a character array.
 */
 
GetLine(s,lim)
    char s[];
    int lim;
{
    int i;
    char c;
 
    for (i = 0; (i < lim-1) && ((c=getchar()) != EOF) && (c != '\n'); ++i)
        s[i] = c;
 
    if (c == '\n')
    {
        s[i] = c;
        ++i;
        s[i] = '\r';
        ++i;
    }
    return(i);
}
 
 
/*
 *Find target address
 *
 * This routine finds the Internet address and port number of
 * the shared printer associated with this print queue.
 *
 * First, the device string (xxx) associated with standard output
 * is retrleved. Then, /etc/printcap is searched for the string
 * "lp-/dev/xxx". Newly defined printcap entries "xn=" and "xp-"
 * are used to define the Internet address and device number,
 * respectively.
 *
 * Inputs:
 * target result data pointer
 * stdout standard output device
 * /dev/xxx directory of device filenames
 * /etc/printcap print spooler database
 *
 * Outputs:
 * function successful,
 * internet address and port number fields of the
 * structure passed as input are filled in.
 *
 * function = -1 if error is encountered.
 * Error is signaled via perror.
 * Structure fields are indeterminate.
 */
 
Find_target_address(target)
    target_address *target;
{
    int pos, string_len;
    char device_name[80], node_id[80], port_no[80];
    ino_t inode;
    struct stat statbuf;
 
/* Find inode pointer for standard output device */
 
    if (fstat(fileno(stdout),&statbuf) != 0) {
        perror("Xyplex shared printer - no inode for output device\n");
        return(ERROR);
    }
    inode = statbuf.st_ino;
 
/*
 * Append the device name string associated with stdout to the string
 * "lp-/dev", leaving result in device name. Then search /etc/printcap
 * for a match on the resulting string. "pos" contains the file pointer
 * for the desired printcap entry.
 */
 
    strcpy(device_name, "lp=/dev/");
    string_len = strlen(device_name);
    if (FindDevice(&device_name[string_len],inode) < 0) {
        perror("Xyplex shared printer - device does not exist\n");
        return(ERROR);
    }
    if ((pos = FindPrintcapEntry(device_name)) < 0) {
        perror("Xyplex shared printer - no printcap entry for device\n");
        return(ERROR);
    }
 
/*
 * Search the printcap entry for "xn- and xp- entrles and extract
 * and convert to network form the strings which follow.
 */
 
    strcpy(node_id, "xn=");
    string_len = strlen(node_id);
    if ((GetString(node_id,pos)) < 0) {
        fprintf(stderr, "Xyplex SPF - no printcap for internet address\n");
        return(ERROR);
    }
    target->internet_address = inet_addr(&node_id[string_len]);
    if (target->internet_address == ERROR) {
        fprintf(stderr, "Xyplex SPF - invalld Internet address in printcap\n");
        return(ERROR);
    }
 
    strcpy(port_no, "xp=");
    string_len = strlen(port_no);
    if (GetString(port_no,pos) < 0 ) {
        fprintf(stderr, "Xyplex SPF - no printcap entry for target port\n");
        return(ERROR);
    }
 
/* An error in the port specification is likely to result in the
 * port number being set to zero. This will often result in a
 * registration configuration error.
 */
    target->port_number = htons(atoi(&port_no[string_len]));
}
 
/*
 * Misc. routines used by Find target address
 */
 
/*
 * FindDevice
 *
 * This routine searches the "/dev directory for the file entry
 * that matches the inode passed as an drgument. This will be the
 * directory entry that corresponds to the device assigned to stdout.
 *
 */
 
FindDevice(s,inode)
char s[];
ino_t inode;
{
    DIR *dirp;
    struct direct *dp;
 
    if ((dirp = opendir("/dev")) == NULL) {
        fprintf(stderr, "Xyplex SPF - unable to access /dev directory\n");
        return(ERROR);
    }
 
    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
        if (dp->d_ino == inode) {
            strcpy(s, dp->d_name);
            closedir(dirp);
            return;
        }
    }
    fprintf(stderr, "Xyplex SPF - unable to find output device\n");
    closedir(dirp);
    return(ERROR);
}
 
FindPrintcapEntry(device_name)
char device_name[];
{
    int not_found,string_pos,pos,temp_pos;
    char c;
    FILE *fp;
 
    if ((fp = fopen("/etc/printcap","r")) == NULL)
        return(ERROR);
 
    pos = 0;
    temp_pos = 0;
    string_pos = 0;
    not_found = TRUE;
 
    while (((c=getc(fp)) != EOF) && not_found)
    {
        temp_pos++;
 
        switch(c)
        {
            case '\\': c = getc(fp); temp_pos++; break;
 
            case '\n': string_pos = 0; pos = temp_pos; break;
 
            default: if (device_name[string_pos] != c)
                            string_pos = 0;
                        else
                        {
                            string_pos++;
                            if (device_name[string_pos] == '\0')
                                not_found = FALSE;
                        }
                        break;
        }
    }
    if (fclose(fp) < 0)
        return(ERROR);
    if (not_found)
        return(ERROR);
 
    return(pos);
}
 
GetString(s,pos)
    char s[];
    int pos;
{
    int found,string_pos,searching,fd;
    char c;
    FILE *fp;
    found = FALSE;
    searching = TRUE;
    string_pos = 0;
 
    if ((fp = fopen("/etc/printcap","r")) == NULL)
        return(ERROR);
 
    fd = fileno(fp);
    if (lseek(fd,pos,L_SET) < 0)
        return(ERROR);
 
    while (((c = getc(fp)) != EOF) && searching)
    {
        switch(c)
        {
            case '\\': c - getc(fp); break;
 
            case '\n': searching = FALSE; break;
 
            case ':': if (found == TRUE)
                        {
                            searching = FALSE;
                            s[string_pos] = '\0';
                        }
                        break;
            default: if (found == FALSE)
                            if (s[string_pos] != c)
                                string_pos = 0;
                            else
                            {
                                string_pos++;
                                if (s[string_pos] == '\0')
                                {
                                    found = TRUE;
                                }
                            }
                        else
                            {
                                s[string_pos] = c;
                                string_pos++;
                            }
                            break;
        }
    }
    if (fclose(fp) < 0)
        return(ERROR);
    if ((found == FALSE) || searching)
        return(ERROR);
}
--------------------------------> cut here <--------------------------------
/*@(#)xyp_filter.c 1.1*/
/*
 * xyp_filter.c - Xyplex Unix Shared Printer Filter
 *
 * 23-Jun-88 Adapted for use in SYSTEM V environment.
 *
 * 21-Feb-89 Change to support Xyplex's assigned UDP port.
 *
 * 2-Nov-89 Rewrite to support both BSD and System V
 *
 */
 
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
 
/* Declare additional constants */
 
#define ERROR -1
#define SUCCESS 0
#define TRUE 1
#define FALSE 0
 
 
/* Declare constants that might be useful in tuning */
 
#define PRINTER_BUFFER_MAX 1000 /* length of the printer buffer */
#define CALLBACK_TIME 30 /* time to wait before re-register */
 
#define REGISTRATION_OK 0x00
#define REGISTRATION_QUEFULL 0x05
#define REGISTRATION_RESPONSE_SIZE 3
#define PACKET_MAX 20
 
/* Declare global storage */
 
u_char doAscii;
u_char printerBuffer[PRINTER_BUFFER_MAX];
int printerBufferIndex;
 
typedef struct
{
   unsigned size; /* matches nbytes arg to write() and read() */
   int index;
   char buffer[PACKET_MAX];
} Packet;
 
int listenSocket;
int printerSocket;
u_long printerInternetAddress;
u_short printerPortNumber;
struct sockaddr_in listenAddr;
 
extern AlarmHandler();
 
.
/*
 * Main procedure
 *
 */
 
main(argc,argv)
int argc;
char *argv[];
 
{
   Initialize();
   GetOptions(argv,argc);
 
   WaitForCallback();
 
   CopyData();
 
   /*
    * Normal termination and closeout
    */
 
   if (close(printerSocket) < 0)
   {
      LogSysError("close of printerSocket failed.");
      close(listenSocket);
      exit(0);
   }
 
   if (close(listenSocket) < 0)
      LogSysError("close of listenSocket failed.");
 
   exit(0);
}
.
AlarmHandler()
/*
 *
 * This routine is defined so that alarm clock signals are trapped
 * and do not force process termination. Alarm signals interrupt
 * recv() and are used to keep it from blocking forever.
 *
 */
{
}
 
 
 
CancelTimer()
{
   alarm(0);
}
 
 
 
CleanupError()
/*
 * Abnormal termination and closeout
 */
{
   close(listenSocket);
   exit(1);
}
.
CopyData()
/*
 * Main data transfer loop.
 * LineFeeds are converted to Carriage Return/Line Feed
 */
{
   int n;
   u_char c;
 
   while ((n = getchar()) != EOF)
   {
      c = n;
 
      if (c == '\n')
      {
        /* If ASCII mode */
        if (doAscii)
             SendPrinter('\r');
      }
      else if (c == 0xff) /* Telnet escape, double it */
      {
         SendPrinter(c);
      }
 
      SendPrinter(c);
   }
 
   FlushPrinter();
}
.
u_char
GetChar(packet)
Packet* packet;
{
   if (packet->index > packet->size)
   {
      LogError("Tried to read past end of packet.");
      exit(1);
   }
 
   return (packet->buffer[packet->index++]);
}
.
/* byte copy from s1 to s2 for len */
 
bcopy(s1, s2, len)
char *s1, *s2;
int len;
{
    int i;
 
    for (i=0; i<len; i++)
        *s2++ = *s1++;
}
 
/* zero out len bytes in s1 */
 
bzero(s1, len)
char *s1;
int len;
{
    int i;
 
    for (i=0; i<len; i++)
        *s1++ = (char) 0;
}
 
/* is this argument some substring of "-graphics" */
 
u_char
IsItGraphics(argument)
char *argument;
{
    char graphics_string[10];
    char *gs;
 
    bcopy("-graphics", graphics_string, 10);
    gs = graphics_string;
 
    while ((*argument == *gs) && (*argument != '\0'))
    {
        argument++;
        gs++;
    }
 
    if (*argument == '\0')
        return(TRUE);
    else return(FALSE);
}
.
GetOptions(argv,argc)
char** argv;
int argc;
{
    int current_arg;
/*
 * Convert input arguments to Internet address, port number and
 * -graphics switch. The -graphics switch may occur anywhere.
 */
   struct hostent* hostEnt;
 
   if (argc < 3)
   {
      LogError("insufficient number of arguments");
      fprintf(stderr, " Usage: xyp_filter <ip-address-or-name> <ip-port> [-graph
ics]\n");
      exit (1);
   }
 
   if (argc > 4)
   {
      LogError("too many arguments");
      fprintf(stderr, " Usage: xyp_filter <ip-address-or-name> <ip-port> [-graph
ics]\n");
      exit (1);
   }
 
   current_arg = 1;
 
   if (IsItGraphics(argv[current_arg]))
   {
        doAscii = FALSE;
        current_arg++;
   }
   printerInternetAddress = inet_addr(argv[current_arg]);
 
   if (printerInternetAddress == ERROR)
   {
      hostEnt = gethostbyname(argv[current_arg]);
 
      if (hostEnt == 0)
      {
         LogError("Bad name or internet address");
         fprintf(stderr, " Usage: xyp_filter <ip-address-or-name> <ip-port> [-gr
aphics]\n");
         exit(1);
      }
 
      bcopy((char *)hostEnt -> h_addr, (char *)&printerInternetAddress, (int)hos
tEnt -> h_length);
   }
 
   current_arg++;
   if (doAscii == (u_char)1)
        if (IsItGraphics(argv[current_arg]))
            {
                doAscii = FALSE;
                current_arg++;
            }
 
   printerPortNumber = htons(atoi(argv[current_arg++]));
 
   if ((argc == 4) && (doAscii == (u_char)1))
   {
        if (IsItGraphics(argv[current_arg]))
            doAscii = FALSE;
        else LogError("bad argument -- ignored\n");
   }
}
 
.
u_short
GetWord(packet)
Packet* packet;
{
   u_short ret;
 
   if (packet->index > packet->size-2)
   {
      LogError("Tried to read past end of packet.");
      exit(1);
   }
 
   ret = (packet->buffer[packet->index++] << 8) & 0xff00;
   ret |= packet->buffer[packet->index++] & 0x00ff;
 
   return ntohs(ret);
}
.
FlushPrinter()
{
   if (write(printerSocket,printerBuffer,printerBufferIndex) < 0)
   {
      LogSysError("write failed.");
      close(printerSocket);
      CleanupError();
   }
}
.
Initialize()
{
    doAscii = 1;
    printerBufferIndex = 0;
}
 
 
LogError(message)
char* message;
{
   time_t t;
 
   t = time(0);
   fprintf(stderr,"%s Xyplex SPF : %s\n",ctime(&t),message);
}
 
 
 
 
LogSysError(message)
char* message;
{
   time_t t;
 
   t = time(0);
   fprintf(stderr,"%s Xyplex SPF : ",ctime(&t));
   perror(message);
   fprintf(stderr,"\n");
}
.
OpenListenSocket()
/*
 * OpenListenSocket - Routine to establish a port for an incoming TCP connection
.
 *
 * First a socket is created, then a free TCP port starting with
 * port number 2000 is found and bound the socket. The port
 * is then activated for an incoming connection by calling
 * listen.
 */
{
   u_short localPort;
 
   listenSocket = socket(AF_INET,SOCK_STREAM,0);
 
   if (listenSocket < 0 )
   {
      LogSysError ("unable to establish socket for connect");
      return(ERROR);
   }
 
   /*
    * Set the circuit keep alive option for the socket
    */
 
   /*
 
    if (setsockopt(listenSocket,SOL_SOCKET,SO_KEEPALIVE,(char *)0,0) < 0)
    {
        LogSysError ("unable to set socket options");
        return(ERROR);
    }
 
     */
 
   bzero((char *)&listenAddr,(int)sizeof(listenAddr));
 
   listenAddr.sin_family = AF_INET;
   listenAddr.sin_addr.s_addr = INADDR_ANY;
 
   localPort = 2000;
 
   while (TRUE)
   {
      listenAddr.sin_port = htons(localPort);
 
      if (bind(listenSocket,(char *)&listenAddr,sizeof(listenAddr)) >=0)
         break;
 
      if (errno != EADDRINUSE && errno != EADDRNOTAVAIL)
      {
         LogSysError("bind failure");
         close(listenSocket);
         return (ERROR);
      }
 
      localPort++;
 
      if (localPort == 65535)
      {
         LogSysError("all TCP ports in use");
         close(listenSocket);
         return (ERROR);
      }
   }
 
   if (listen(listenSocket,1) < 0)
   {
      LogSysError("listen failure");
      close(listenSocket);
      return (ERROR);
   }
   return (SUCCESS);
}
.
PutChar(packet,c)
Packet* packet;
u_char c;
{
   if (packet->index > PACKET_MAX-1)
   {
      LogError("Tried to write past end of packet.");
      exit(1);
   }
 
   packet->buffer[packet->index++] = c;
   packet->size++;
}
 
 
PutWord(packet,w)
Packet* packet;
u_short w;
{
   u_short temp;
 
   if (packet->index > PACKET_MAX-1)
   {
      LogError("Tried to write past end of packet.");
      exit(1);
   }
 
   temp = htons(w);
   packet->buffer[packet->index++] = (temp & 0xff00) >> 8;
   packet->size++;
   packet->buffer[packet->index++] = temp & 0x00ff;
   packet->size++;
}
.
Register(TCPport)
/*
 * Register with shared printer
 *
 * Send registration message every two seconds until a response
 * is received. If response is ok, registration is complete. If
 * response is other than reqistration queue full, abort the
 * process. If the response is a full queue, keep trying; but don't
 * log any errors.
 *
 * The destination UDP port is now 173, the XYPLEX multiplexed MCK
 * protocol port. The XYPLEX protocol number for printer MCK
 * registration (1) is now included in the request. MCK
 *
 */
u_short TCPport;
{
   Packet registrationRequest;
   Packet registrationResponse;
 
   u_short result;
 
   int retryCount;
   int registrationSocket;
   struct sockaddr_in registrationAddr;
 
   /* Establish UDP socket */
 
   bzero((char *) &registrationAddr, sizeof(registrationAddr));
   registrationAddr.sin_family = AF_INET;
   registrationAddr.sin_addr.s_addr = printerInternetAddress;
   registrationAddr.sin_port = htons(173);
 
   registrationSocket = socket(AF_INET,SOCK_DGRAM,0);
 
   if (registrationSocket < 0)
   {
      LogSysError("no socket for registration");
      return (ERROR);
   }
 
   if (connect(registrationSocket,&registrationAddr,
           sizeof(registrationAddr)) < 0)
   {
      close(registrationSocket);
      LogSysError("cannot connect to UDP socket");
      return(ERROR);
   }
 
 
   /* Build and send the registration message */
 
   registrationRequest.index = 0;
   registrationRequest.size = 0;
 
   PutChar(&registrationRequest, 1); /* our protocol type */
   PutChar(&registrationRequest, 0); /* pad */
   PutWord(&registrationRequest, printerPortNumber);
   PutWord(&registrationRequest, TCPport);
   PutWord(&registrationRequest, 0);
 
   if (write(registrationSocket,
         registrationRequest.buffer,
         registrationRequest.size) < 0)
   {
      close(registrationSocket);
      LogSysError("registration write failed");
      return(ERROR);
   }
 
   retryCount = 0;
 
   StartTimer(2);
 
   while (TRUE)
   {
      registrationResponse.size = REGISTRATION_RESPONSE_SIZE;
      registrationResponse.index = 0;
 
      if (recv(registrationSocket,
           registrationResponse.buffer,
           registrationResponse.size,0) > 0)
      {
         GetChar(&registrationResponse); /* move past protocol */
 
         if (result = GetWord(&registrationResponse) == REGISTRATION_OK)
         {
            CancelTimer();
            break;
         }
         else
         {
            if (result != REGISTRATION_QUEFULL) /* not queue full */
            {
               LogError("configuration error");
               CancelTimer();
               return (ERROR);
            }
            else /* don't log if queue full */
            {
               retryCount = 0;
            }
         }
      }
      else
      {
         if (errno == EINTR) /* if timeout */
         {
            write(registrationSocket,
              registrationRequest.buffer,
              registrationRequest.size);
 
            StartTimer(2);
            retryCount++; /* log every 60 trys */
 
            if (retryCount > 60)
            {
               retryCount = 0;
               LogError("unable to register with server");
            }
         }
         else
         {
            LogSysError("error on UDP receive");
            CancelTimer();
            return (ERROR);
         }
      }
   }
 
   close(registrationSocket);
   return (SUCCESS);
}
.
SendPrinter(c)
u_char c;
{
   if (printerBufferIndex >= PRINTER_BUFFER_MAX)
   {
      if (write(printerSocket,printerBuffer,printerBufferIndex) < 0)
      {
         LogSysError("write failed.");
         close(printerSocket);
         CleanupError();
      }
 
      printerBufferIndex = 0;
   }
 
   printerBuffer[printerBufferIndex++] = c;
}
.
StartTimer(t)
int t;
{
   signal(SIGALRM,AlarmHandler);
   alarm(t);
}
.
WaitForCallback()
{
   /*
    * Establish a port for the callback connection from the cluster
    * controller.
    */
 
   if (OpenListenSocket() == ERROR)
      exit(1);
 
   /*
    * Register with the shared printer port on the cluster controller.
    * The registration fails if the response indicates a configuration
    * error.
    * The registration process continues indefinitly if no response is
    * received at all.
    */
 
   if (Register(listenAddr.sin_port) < 0)
      CleanupError();
 
   StartTimer(CALLBACK_TIME);
 
   /*
    * Wait for connect callback from cluster or the callback timeout.
    * If connect callback does not complete in a reasonable period of time
    * re-register with the cluster controller. The callback might not
    * complete within the specified time if either the cluster controller
    * has crashed and restarted or the list of jobs takes a significant
    * time to print. Re-registration in the former case is most desirable
    * and harmless in the latter case.
    */
 
   while (TRUE)
   {
      printerSocket = accept(listenSocket,0,0);
      CancelTimer();
 
      /* Connection complete? */
      if (printerSocket > 0 )
         break; /* connection complete */
 
      if (errno == EINTR) /* timeout? */
      {
         if (Register(listenAddr.sin_port) < 0)
            CleanupError();
 
         StartTimer(CALLBACK_TIME);
      }
      else
      {
         LogSysError("accept failed.");
         CleanupError();
      }
   }
}
--------------------------------> cut here <--------------------------------
 
I hope this helps. If you have TCP/IP support, this should be mentioned in
the manual. If you don't, contact your Xyplex salesperson and find out if
it is easy and/or cheap to upgrade.
 
----------------------------------------------------------------------------
Doug Neuhauser Div. of Geological and Planetary Sciences
doug@seismo.gps.caltech.edu California Institute of Technology
818-356-3993 MS 252-21, Pasadena, CA 91125



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:06:10 CDT