This is uw_pcl.c in view mode; [Download] [Up]
/* * uw_pcl - protocol handling for UW * * Copyright 1985,1986 by John D. Bruner. All rights reserved. Permission to * copy this program is given provided that the copy is not sold and that * this copyright notice is included. */ #include <sys/types.h> #include <sys/ioctl.h> #include <errno.h> #include "uw_param.h" #include "uw_clk.h" #include "uw_opt.h" #include "uw_win.h" #include "uw_pcl.h" #include "openpty.h" #define XON 0021 /* ASCII XON */ #define XOFF 0023 /* ASCII XOFF */ #define RUB 0177 /* ASCII RUBout */ #define META 0200 /* "meta" bit for whatever it's worth */ /* * Protocol negotiation is performed by a finite state machine (implemented * in pcl_haggle()). The states are defined as the enumerated type * "pnstate_t". The inputs have type "pnreq_t". */ typedef enum { PNST_NOP, /* no protocol negotiation */ PNST_AWAIT, /* timing out an ASKPCL */ PNST_CWAIT, /* timing out a CANPCL */ PNST_OK, /* negotiations completed */ PNST_FAIL /* negotiation failed */ } pnstate_t; typedef unsigned short pnreq_t; /* finite state machine requests */ #define PNRQ_PCL 0000377 /* protocol mask */ #define PNRQ_CMD 0177400 /* command mask: */ #define PNRQ_NONE (pnreq_t)(0<<8) /* no request */ #define PNRQ_START (pnreq_t)(1<<8) /* start negotiation */ #define PNRQ_AWAIT (pnreq_t)(2<<8) /* timeout waiting for ASKPCL */ #define PNRQ_ASK (pnreq_t)(3<<8) /* process received ASKPCL */ #define PNRQ_CAN (pnreq_t)(4<<8) /* process received CANPCL */ #define PNRQ_SET (pnreq_t)(5<<8) /* process received SETPCL */ #define PNRQ_CWAIT (pnreq_t)(6<<8) /* timeout waiting for CANPCL */ #define PNRQ_INIT (pnreq_t)(7<<8) /* initialize everything */ static int p1_ctlch[] = { -1, P1_IAC, XON, XOFF, -1, -1, -1, -1 }; extern void p1_entry(), p1_renew(), p2_renew(); extern struct window *p1_neww(), *p2_neww(); extern void p1_killw(), p1_xmit(), p1_recv(); extern void p1_askpcl(), p1_canpcl(), p1_setpcl(); extern void p2_recv(), p2_chkopt(), p2_sendopt(); static struct protocol pcl_table[] = { { ' ', P1_NWINDOW, p1_ctlch, sizeof p1_ctlch / sizeof p1_ctlch[0], p1_entry, NULL, p1_renew, p1_neww, p1_killw, p1_xmit, p1_recv, NULL, NULL, p1_askpcl, p1_canpcl, p1_setpcl }, { '!', P2_NWINDOW, p1_ctlch, sizeof p1_ctlch / sizeof p1_ctlch[0], p1_entry, NULL, p2_renew, p2_neww, p1_killw, p1_xmit, p2_recv, p2_chkopt, p2_sendopt, p1_askpcl, p1_canpcl, p1_setpcl }, }; struct protocol *protocol = pcl_table; /* * Two "current" windows are defined: the current input window (for * input from the Macintosh) and the current output window (for output * to the Macintosh). */ static struct { struct window *in; struct window *out; } curwin; pcl_entry(mfd) register fildes_t mfd; { /* * This routine is called to start up protocol handling. We always * start with protocol 1 (the original UW protocol). */ protocol = pcl_table; pcl_haggle(mfd, PNRQ_INIT); if (protocol->p_entry) (*protocol->p_entry)(mfd); } pcl_exit(mfd) register fildes_t mfd; { /* * This routine is called when we shut down (just before the server * exits). */ if (protocol->p_exit) (*protocol->p_exit)(mfd); protocol = pcl_table; } static pcl_newpcl(newproto) struct protocol *newproto; { extern void rc_kludge(); /* * Switch to new protocol "newproto". Right now we can get away * with just changing the value of "protocol". Eventually we will * probably want to call protocol-dependent functions to shut down * the old protocol and start up the new one. */ protocol = newproto; /* * This is a horrible kludge. See rc_kludge() in "main.c" for * further details. */ rc_kludge(); } static void pcl_tohaggle(arg) register toarg_t arg; { /* * This is a kludge to get around the single-argument restriction * on clk_timeout. We split the argument "arg" into two 16-bit * pieces and invoke pcl_haggle. */ pcl_haggle((fildes_t)((arg>>16)&0177777), (pnreq_t)(arg&0177777)); } static pcl_haggle(mfd, req) fildes_t mfd; register pnreq_t req; { register struct protocol *p, *q; register char pname; register int request; static pnstate_t pnstate; static int waitcnt; /* * This routine implements the finite-state machine that handles * protocol negotiation. This routine is called by routines which * recognize incoming protocol commands and at 5 second intervals * when negotiations are in progress. The current protocol is * described by the variable "protocol". */ if (req == PNRQ_INIT) { waitcnt = 0; pnstate = PNST_NOP; req = PNRQ_NONE; } if (!(p = protocol) || !p->p_askpcl || !p->p_canpcl || !p->p_setpcl) { req = PNRQ_NONE; } else { pname = req & PNRQ_PCL; request = req & PNRQ_CMD; switch (request) { case PNRQ_START: /* start protocol negotiation */ /* * The Macintosh is responsible for starting protocol * negotiation (if it wants something other than the * standard protocol). This code is present for * purposes of exposition only. */ (*p->p_askpcl)(mfd); req = PNRQ_AWAIT | pname; waitcnt = 0; pnstate = PNST_AWAIT; break; case PNRQ_AWAIT: /* timeout an ASKPCL */ /* * This state also is not reached on the host. */ if (pnstate == PNST_AWAIT) { if (++waitcnt > 3) { pnstate = PNST_FAIL; req = PNRQ_NONE; } else (*p->p_askpcl)(mfd); } else req = PNRQ_NONE; break; case PNRQ_ASK: /* handle received ASKPCL */ q = pcl_table+sizeof pcl_table/sizeof pcl_table[0] - 1; (*p->p_canpcl)(mfd, q->p_name); pnstate = PNST_CWAIT; req = PNRQ_CWAIT | q->p_name; waitcnt = 0; break; case PNRQ_CAN: /* handle received CANPCL */ for (q=pcl_table+sizeof pcl_table/sizeof pcl_table[0]-1; q > pcl_table && q->p_name > pname; q--) ; if (q->p_name == pname || q == pcl_table) { (*p->p_setpcl)(mfd, q->p_name); pcl_newpcl(q); pnstate = PNST_OK; req = PNRQ_NONE; } else { (*p->p_canpcl)(mfd, q->p_name); pnstate = PNST_CWAIT; req = PNRQ_CWAIT | q->p_name; waitcnt = 0; } break; case PNRQ_CWAIT: /* timeout a CANPCL */ if (pnstate == PNST_CWAIT) { if (++waitcnt > 3) { pnstate = PNST_FAIL; req = PNRQ_NONE; } else (*p->p_canpcl)(mfd, pname); } else req = PNRQ_NONE; break; case PNRQ_SET: /* handle a received SETPCL */ for (q=pcl_table+sizeof pcl_table/sizeof pcl_table[0]-1; q > pcl_table && q->p_name != pname; q--) ; if (q->p_name == pname) { pcl_newpcl(q); pnstate = PNST_OK; req = PNRQ_NONE; } else { /* * We are in trouble now -- the Mac has * instructed us to switch to a protocol * that we can't support. We switch back * to protocol 1 and hope that our message * to the Mac (telling it to switch to * protocol 1) will be interpreted correctly. */ pnstate = PNST_FAIL; req = PNRQ_NONE; (*p->p_setpcl)(mfd, ' '); if (p != pcl_table) pcl_newpcl(pcl_table); } break; } if (req != PNRQ_NONE) (void)clk_timeout(5*CLK_HZ, pcl_tohaggle, (toarg_t)(((long)mfd<<16)|req)); } } static void p1_entry(mfd) fildes_t mfd; { static char cmdbuf[2] = { P1_IAC }; cmdbuf[1] = P1_DIR_HTOM|P1_FN_MAINT|P1_MF_ENTRY; (void)write(mfd, cmdbuf, sizeof cmdbuf); } static struct window * p1_neww(mfd, wclass, wtype, wnum, wid, datafd, ctlfd) fildes_t mfd; wclass_t wclass; wtype_t wtype; nwin_t wnum; long wid; fildes_t datafd; fildes_t ctlfd; { register struct window *w; static char cmdbuf[2] = { P1_IAC, 0 }; /* * Create a new window for the host. This routine is not called when * the Macintosh creates a window. */ w = win_neww(wclass, wtype, wnum, protocol->p_maxwin, wid, datafd, ctlfd, (struct woptdefn *)0); if (w) { cmdbuf[1] = P1_DIR_HTOM|P1_FN_NEWW|WIN_NUM(w); (void)write(mfd, cmdbuf, sizeof cmdbuf); } return(w); } static void p1_killw(mfd, w) register struct window *w; { static char cmdbuf[] = { P1_IAC, P1_DIR_HTOM|P1_FN_KILLW }; /* * Kill window "w" and tell the Macintosh to do the same. */ if (w && w->w_alloc) { if (curwin.in == w) curwin.in = (struct window *)0; if (curwin.out == w) curwin.out = (struct window *)0; cmdbuf[1] = P1_DIR_HTOM|P1_FN_KILLW|WIN_NUM(w); (void)write(mfd, cmdbuf, sizeof cmdbuf); win_killw(w); } } static void p1_xmit(mfd, w) fildes_t mfd; register struct window *w; { register char *cp, *cq; register int i, len; char ibuf[32], obuf[32]; static char refresh; static char cmdbuf[] = { P1_IAC, 0 }; extern int errno; /* * Transmit data to the Macintosh (via file descriptor "mfd) * on behalf of window "w". Be sure to convert any embedded * control characters and meta characters. * * Note that the input/output buffers should NOT be very large. * It is undesirable to perform large reads and effectively * "lock out" all other file descriptors. The chosen size * should preserve a reasonable amount of efficiency. * * The UW protocol only requires an OSELW command when the * output window changes. We issue this command more often * to "refresh" the Mac's idea of what the output window is. * This helps (slightly) to overcome spurious output redirects * caused by a noisy line. */ if (w && w->w_alloc) { if (curwin.out != w || ++refresh == 0) { refresh = 0; curwin.out = w; cmdbuf[1] = P1_DIR_HTOM|P1_FN_OSELW|WIN_NUM(w); (void)write(mfd, cmdbuf, sizeof cmdbuf); } cq = obuf; if ((len = read(w->w_datafd, ibuf, sizeof ibuf)) < 0 && errno != EWOULDBLOCK) (*protocol->p_killw)(mfd, w); for (cp=ibuf; cp < ibuf+len; cp++) { if (*cp&META) { if (cq > obuf) { (void)write(mfd, obuf, cq-obuf); cq = obuf; } cmdbuf[1] = P1_DIR_HTOM|P1_FN_META; (void)write(mfd, cmdbuf, sizeof cmdbuf); *cp &= ~META; } i = -1; if (*cp == RUB || *cp < ' ') { i = protocol->p_szctlch - 1; while (i >= 0 && protocol->p_ctlch[i] != *cp) i--; } if (i >= 0) { if (cq > obuf) { (void)write(mfd, obuf, cq-obuf); cq = obuf; } cmdbuf[1] = P1_DIR_HTOM|P1_FN_CTLCH|i; (void)write(mfd, cmdbuf, sizeof cmdbuf); } else { *cq++ = *cp; if (cq >= obuf+sizeof obuf) { (void)write(mfd, obuf, cq-obuf); cq = obuf; } } } if (cq > obuf) (void)write(mfd, obuf, cq-obuf); } else (void)read(w->w_datafd, ibuf, sizeof ibuf); } static void p1_recv(mfd, cbuf, clen) fildes_t mfd; char *cbuf; int clen; { register int len; register char *buf, *cp, *cq; register struct window *w; nwin_t wnum; auto int nready; char ibuf[512], obuf[512]; static int seen_iac, seen_meta; static pnreq_t pnrq_cmd; static char cmdbuf[2] = { P1_IAC }; /* * The received bytestream is examined. Non-command bytes are * written to the file descriptor corresponding to the current * "input" window (relative to the Macintosh -- the window the * user types input to). * * If "clen" is nonzero, then the contents of the buffer "cbuf" * are processed before any input is read. */ if (ioctl(mfd, (int)FIONREAD, (char *)&nready) < 0) { perror("FIONREAD"); return; } nready += clen; for (cq = obuf; nready > 0; nready -= len) { if (clen > 0) { len = clen; buf = cbuf; clen = 0; } else { if (nready > sizeof ibuf) len = read(mfd, ibuf, sizeof ibuf); else len = read(mfd, ibuf, nready); if (len <= 0) { perror("read"); return; } buf = ibuf; } for (cp=buf; cp < buf+len; cp++) { if (pnrq_cmd) { pcl_haggle(mfd, pnrq_cmd|*cp); pnrq_cmd = 0; /* pcl_haggle may have changed the protocol */ if (protocol != pcl_table) { if (protocol->p_recv) (*protocol->p_recv)(mfd, cp+1, buf+len-cp-1); return; } } else if (seen_iac) { if ((*cp&P1_DIR) == P1_DIR_MTOH) { if (cq > obuf) { (void)write(curwin.in->w_datafd, obuf, cq-obuf); cq = obuf; } switch (*cp & P1_FN) { case P1_FN_NEWW: wnum = *cp & P1_WINDOW; if (!wnum) break; w = WIN_PTR(wnum); if (w->w_alloc) break; if (!win_neww(WC_INTERNAL, defwtype, wnum, protocol->p_maxwin, 0L, (fildes_t)-1, (fildes_t)-1, (struct woptdefn *)0)) { cmdbuf[1] = P1_DIR_HTOM| P1_FN_KILLW|wnum; (void)write(mfd, cmdbuf, sizeof cmdbuf); } break; case P1_FN_KILLW: wnum = *cp & P1_WINDOW; if (!wnum) break; win_killw(WIN_PTR(wnum)); break; case P1_FN_ISELW: wnum = *cp & P1_WINDOW; if (!wnum) break; w = WIN_PTR(wnum); if (w->w_alloc) curwin.in = w; else curwin.in = NULL; break; case P1_FN_META: seen_meta = 1; break; case P1_FN_CTLCH: *cq = protocol->p_ctlch[*cp&P1_CC]; if (seen_meta) { seen_meta = 0; *cq |= META; } if (curwin.in) cq++; break; case P1_FN_MAINT: switch (*cp & P1_MF) { case P1_MF_ENTRY: (*protocol->p_renew)(mfd); break; case P1_MF_ASKPCL: pcl_haggle(mfd,PNRQ_ASK); break; case P1_MF_CANPCL: pnrq_cmd = PNRQ_CAN; break; case P1_MF_SETPCL: pnrq_cmd = PNRQ_SET; break; case P1_MF_EXIT: done(0); break; } break; } } seen_iac = 0; } else if (*cp == P1_IAC) seen_iac++; else { if (seen_meta) { seen_meta = 0; *cq = *cp | META; } else *cq = *cp; if (curwin.in) { if (++cq >= obuf+sizeof obuf) { (void)write(curwin.in->w_datafd, obuf, cq-obuf); cq = obuf; } } } } } if (cq > obuf) (void)write(curwin.in->w_datafd, obuf, cq-obuf); } static void p1_askpcl(mfd) fildes_t mfd; { static char cmdbuf[2] = { P1_IAC,P1_DIR_HTOM|P1_FN_MAINT|P1_MF_ASKPCL }; (void)write(mfd, cmdbuf, sizeof cmdbuf); } static void p1_canpcl(mfd, pname) fildes_t mfd; char pname; { static char cmdbuf[3] = { P1_IAC,P1_DIR_HTOM|P1_FN_MAINT|P1_MF_CANPCL }; cmdbuf[2] = pname; (void)write(mfd, cmdbuf, sizeof cmdbuf); } static void p1_setpcl(mfd, pname) fildes_t mfd; char pname; { static char cmdbuf[3] = { P1_IAC,P1_DIR_HTOM|P1_FN_MAINT|P1_MF_SETPCL }; cmdbuf[2] = pname; (void)write(mfd, cmdbuf, sizeof cmdbuf); } static void p1_renew(mfd) fildes_t mfd; { register struct window *w; static char cmdbuf[2] = { P1_IAC }; /* * Re-init (re-NEW) an existing connection. Send a NEWW command * for each existing window. This function is invoked when the * Macintosh sends an ENTRY maintenance command. */ for (w=window; w < window+protocol->p_maxwin; w++) { if (w->w_alloc) { win_renew(w, 0); cmdbuf[1] = P1_DIR_HTOM|P1_FN_NEWW|WIN_NUM(w); (void)write(mfd, cmdbuf, sizeof cmdbuf); } } } static void p2_renew(mfd) fildes_t mfd; { register struct window *w; static char cmdbuf[3] = { P2_IAC }; /* * Re-init (re-NEW) an existing connection. Send a NEWW command * for each existing window. This function is invoked when the * Macintosh sends an ENTRY maintenance command. */ for (w=window; w < window+protocol->p_maxwin; w++) { if (w->w_alloc) { win_renew(w, 1); cmdbuf[1] = P2_DIR_HTOM|P2_FN_NEWW|WIN_NUM(w); cmdbuf[2] = w->w_type + ' '; (void)write(mfd, cmdbuf, sizeof cmdbuf); } } } static struct window * p2_neww(mfd, wclass, wtype, wnum, wid, datafd, ctlfd) fildes_t mfd; wclass_t wclass; wtype_t wtype; nwin_t wnum; long wid; fildes_t datafd; fildes_t ctlfd; { register struct window *w; static char cmdbuf[3] = { P2_IAC }; /* * Create a new window as requested by the host. This routine is not * called when the Macintosh creates a window. */ w = win_neww(wclass, wtype, wnum, protocol->p_maxwin, wid, datafd, ctlfd, (struct woptdefn *)0); if (w) { cmdbuf[1] = P2_DIR_HTOM|P2_FN_NEWW|WIN_NUM(w); cmdbuf[2] = ' ' + wtype; (void)write(mfd, cmdbuf, sizeof cmdbuf); } return(w); } static void p2_chkopt(mfd) fildes_t mfd; { register struct window *w; nwin_t maxwin; /* * Ideally, this routine would call a routine in the window * module (perhaps win_chkopt()), passing the maximum window * number as one argument. The "for" loop would be in that * routine. However, I'm not willing to accept the overhead * for that conceptual nicety. */ maxwin = protocol->p_maxwin; for (w=window; w < window+maxwin; w++) if (w->w_alloc) opt_scan((caddr_t)w, &w->w_optdefn, p2_sendopt, mfd, P2_FN_WOPT|WIN_NUM(w)); } static void p2_sendopt(mfd, fn, buf, len) fildes_t mfd; int fn; register char *buf; register int len; { register char *cp; register int i; char outbuf[512]; /* * Encode and transmit the option string contained in "buf". The * initial command (which will be P2_FN_WOPT|WIN_NUM(w)) is * contained in "fn". * * The caller is responsible for handing us a correctly-formed * option string. This routine merely performs the protocol encoding * which is required for control and meta characters. */ curwin.out = NULL; outbuf[0] = P2_IAC; outbuf[1] = fn|P2_DIR_HTOM; for (cp=outbuf+2; len > 0; buf++,len--) { if (cp > outbuf+sizeof outbuf - 4) { (void)write(mfd, outbuf, cp-outbuf); cp = outbuf; } if (*buf & META) { *cp++ = P2_IAC; *cp++ = P2_DIR_HTOM|P2_FN_META; *buf &= ~META; } i = -1; if (*buf == RUB || *buf < ' ') { i = protocol->p_szctlch - 1; while (i >= 0 && protocol->p_ctlch[i] != *buf) i--; } if (i >= 0) { *cp++ = P2_IAC; *cp++ = P2_DIR_HTOM|P2_FN_CTLCH|(i&7); } else *cp++ = *buf; } if (cp > outbuf) (void)write(mfd, outbuf, cp-outbuf); } static void p2_recv(mfd, cbuf, clen) fildes_t mfd; char *cbuf; int clen; { register int len; register char *buf, *cp, *cq; register struct window *w; register char c; nwin_t wnum; auto int nready; char ibuf[512], obuf[512]; static int seen_iac, seen_meta, is_option; static pnreq_t pnrq_cmd; static nwin_t neww; static char cmdbuf[2] = { P2_IAC }; /* * The received bytestream is examined. Non-command bytes are * written to the file descriptor corresponding to the current * "input" window (relative to the Macintosh -- the window the * user types input to). * * If "clen" is nonzero, then the contents of the buffer "cbuf" * are processed before any input is read. */ if (ioctl(mfd, (int)FIONREAD, (char *)&nready) < 0) { perror("FIONREAD"); return; } nready += clen; for (cq = obuf; nready > 0; nready -= len) { if (clen > 0) { len = clen; buf = cbuf; clen = 0; } else { if (nready > sizeof ibuf) len = read(mfd, ibuf, sizeof ibuf); else len = read(mfd, ibuf, nready); if (len <= 0) { perror("read"); return; } buf = ibuf; } for (cp=buf; cp < buf+len; cp++) { if (pnrq_cmd) { pcl_haggle(mfd, pnrq_cmd|*cp); pnrq_cmd = 0; /* pcl_haggle may have changed the protocol */ if (protocol != pcl_table) { if (protocol->p_recv) (*protocol->p_recv)(mfd, cp+1, buf+len-cp-1); return; } } else if (neww) { w = WIN_PTR(neww); if (!w->w_alloc && !win_neww(WC_INTERNAL, (wtype_t)(*cp-' '), neww, protocol->p_maxwin, 0L, (fildes_t)-1, (fildes_t)-1, (struct woptdefn *)0)) { cmdbuf[1] = P2_DIR_HTOM| P2_FN_KILLW|neww; (void)write(mfd, cmdbuf, sizeof cmdbuf); } neww = 0; } else if (seen_iac) { if ((*cp&P2_DIR) == P2_DIR_MTOH) { c = *cp & P2_FN; if (is_option && c!=P2_FN_META && c!=P2_FN_CTLCH) { opt_iflush(); is_option = 0; } if (cq > obuf) { (void)write(curwin.in->w_datafd, obuf, cq-obuf); cq = obuf; } switch (*cp & P2_FN) { case P2_FN_NEWW: neww = *cp & P2_WINDOW; break; case P2_FN_WOPT: wnum = *cp & P2_WINDOW; if (!wnum) break; w = WIN_PTR(wnum); if (!w->w_alloc) { curwin.in = NULL; break; } is_option = 1; opt_istart((caddr_t)w, &w->w_optdefn); break; case P2_FN_KILLW: wnum = *cp & P2_WINDOW; if (!wnum) break; win_killw(WIN_PTR(wnum)); break; case P2_FN_ISELW: wnum = *cp & P2_WINDOW; if (!wnum) break; w = WIN_PTR(wnum); if (w->w_alloc) curwin.in = w; else curwin.in = NULL; break; case P2_FN_META: seen_meta = 1; if ((*cp&P2_CC) == 0) break; /* no break */ case P2_FN_CTLCH: c=protocol->p_ctlch[*cp&P2_CC]; if (seen_meta) { seen_meta = 0; c |= META; } if (is_option) is_option=opt_input(c); else if (curwin.in) *cq++ = c; break; case P2_FN_MAINT: switch (*cp & P2_MF) { case P2_MF_ENTRY: (*protocol->p_setpcl)(mfd, protocol->p_name); (*protocol->p_renew)(mfd); break; case P2_MF_ASKPCL: pcl_haggle(mfd,PNRQ_ASK); break; case P2_MF_CANPCL: pnrq_cmd = PNRQ_CAN; break; case P2_MF_SETPCL: pnrq_cmd = PNRQ_SET; break; case P2_MF_EXIT: done(0); break; } break; } } seen_iac = 0; } else if (*cp == P2_IAC) seen_iac++; else { if (seen_meta) { c = *cp | META; seen_meta = 0; } else c = *cp; if (is_option) is_option = opt_input(c); else if (curwin.in) *cq++ = c; if (cq >= obuf+sizeof obuf) { (void)write(curwin.in->w_datafd, obuf, cq-obuf); cq = obuf; } } } } if (cq > obuf) (void)write(curwin.in->w_datafd, obuf, cq-obuf); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.