Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.2 9/5/84; site asuvax.UUCP Path: utzoo!watmath!clyde!burl!ulysses!allegra!mit-eddie!think!harvard!seismo!hao!noao!terak!asuvax!system From: system@asuvax.UUCP (Marc Lesure) Newsgroups: net.sources Subject: 4.2 Window Management System (wms part 3 of 4) Message-ID: <292@asuvax.UUCP> Date: Fri, 20-Sep-85 01:39:05 EDT Article-I.D.: asuvax.292 Posted: Fri Sep 20 01:39:05 1985 Date-Received: Sun, 22-Sep-85 06:38:48 EDT Distribution: net Organization: Arizona State Univ, Tempe Lines: 896 wms part 3 of 4 If your site decides on implementing wms, please send mail to the author so he can send updates (if any) in the future. Also, please send all bug reports, problems, fixes, etc. to the author rather than posting them to net.sources.bugs. Marc Lesure System Manager Engineering Computer Center Arizona State University Tempe, Arizona UUCP: ...!{ucbvax,ihnp4}!arizona!asuvax!lesure ...!ihnp4!terak!asuvax!lesure CSNET: lesure@asu ARPA: lesure%asu@csnet-relay ------------------------------------------------------------- # This is a shell archive. Remove all lines before this one. # Use 'sh ' to unpack the contents. # # contents: # release/source # release/source/NOTES # release/source/msh # release/source/msh/max.msh.h # release/source/msh/msh.bind.c # release/source/msh/msh.c # release/source/msh/msh.h # release/source/msh/util.h # echo x - release/source mkdir release/source echo x - release/source/NOTES sed 's/^@@//' > "release/source/NOTES" << '@@ END-OF-FILE' /****************************************************************************\ * Copyright 1985 by George Nelan, Arizona State University. * * All rights reserved. Permission to use, modify, and copy these programs * * and documentation is granted, provided that the copy is not sold and * * that this copyright and permission notice appear on all copies. * \****************************************************************************/ ------------------------------------------------------------------------------ General notes & philosophy... ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ The source codes reference include files which they think are in their own local directory. These files are symbolically linked to where they are relative to the distribution directory structure. This needs to be considered if the structure is disturbed (a relink may be necessary - see the 'relink' script in this directory). ------------------------------------------------------------------------------ For wty, the requirement that terminals possess a controllable scrolling region can be extended to cover tvi-925 and other types. This would require either a unique format for the 'cs' cap (of the actual device), or perhaps more appropriately, a new cap for wty only (so that other programs do not get confused by the non-standard 'cs' format). This would allow wty to distinguish between various methods of 'cs'. ------------------------------------------------------------------------------ The initial version of wty used the *curses* library and was found to be unsatisfactory for these reasons: o Programs which do cursor motion usually optimize such motion themselves, possibly via curses itself. No redundancy in this area was desired. o Curses' idea of scrolling windows means to redraw a portion of the screen. For arbitrary size windows on a vanilla terminal, there is no other way. Simply by restricting window dimensions however, it is possible to efficiently implement truly scrollable windows via multiplexing of the scrolling region. Hence the lower level termcap library was used instead. However, not using curses makes it more difficult to implement internal buffering & window refresh. It would be nice to be able to scroll and refresh internal buffers. ------------------------------------------------------------------------------ The method of communicating the current window size to a sub-shell needs to be thought about more carefully. The "setenv" message is the simplest way, but leaves much to be desired. ------------------------------------------------------------------------------ Msh has a hook in it to keep roots from using it. When our own local root tested it, we found strange things happening to various sockets. Since I am not the root here, it is difficult for me to validate possible solutions. I believe such problems have been resolved, but I cannot be sure. Therefore, remove this hook at your own risk. (The hook is in init_msh(), the problem seemed to be in do_QX() and/or do_DS()). ------------------------------------------------------------------------------ Signal handling is minimal. Some thought needs to be done about putting msh to sleep when IO inactivity exceeds some threshold, waking up of course on resumed IO activity. ------------------------------------------------------------------------------ In both wty & msh, input command procedures are intentionally split into duals so that one procedure manages input parameters (if any), checking syntax and semantics; and the other manages the function itself. This method introduces redundancy in some cases; but for the sake of consistency, all future expansions should follow the same style. At least this allows the second procedure to be used independently of the first. ------------------------------------------------------------------------------ The following graph illustrates the primary flow of control for msh: main: init_msh loop slave_driver (for next slave i) master_driver get_master, read tty exec_cmd | put_slave, write pty get_slave(i), read pty(i) put_master |__ ------------------------------------------------------------------------------ The following graph illustrates the primary flow of control for wty/msh: main: init_msh init_wty loop screen_driver get_port (if pq empty) slave_driver (for next slave i) master_driver get_master (if mq empty) keyboard_driver get_keyboard, read tty put_port put_slave, write pty get_slave(i), read pty(i) put_master put_screen(i), write tty |__ @@ END-OF-FILE echo x - release/source/msh mkdir release/source/msh echo x - release/source/msh/max.msh.h ln -s ../../public/wms/max.msh.h release/source/msh/max.msh.h echo x - release/source/msh/msh.bind.c sed 's/^@@//' > "release/source/msh/msh.bind.c" << '@@ END-OF-FILE' /****************************************************************************\ * Copyright 1985 by George Nelan, Arizona State University. * * All rights reserved. Permission to use, modify, and copy these programs * * and documentation is granted, provided that the copy is not sold and * * that this copyright and permission notice appear on all copies. * \****************************************************************************/ /* AUTHOR: GEORGE NELAN */ /* discreet version msh binder */ #include extern init_msh(); extern slave_driver(); #include /****************************************************************************/ main (argc,argv) int argc; char *argv[]; { init_msh(); for (;;) { slave_driver(); } } /* main */ /****************************************************************************/ int get_master(chp) register char *chp; /* return > 0 if std input ready, * else return <= 0 if not ready or error. */ { int tlen; if (ioctl(0,FIONREAD,&tlen) == (-1)) return(-1); if ((tlen > 0) && ((tlen = read(0,chp,1)) == (-1))) return(-1); return(tlen); } /* get_master */ /****************************************************************************/ put_master(chp,len) register char *chp; register int len; { write(1,chp,len); } /* put_master */ @@ END-OF-FILE echo x - release/source/msh/msh.c sed 's/^@@//' > "release/source/msh/msh.c" << '@@ END-OF-FILE' /****************************************************************************\ * Copyright 1985 by George Nelan, Arizona State University. * * All rights reserved. Permission to use, modify, and copy these programs * * and documentation is granted, provided that the copy is not sold and * * that this copyright and permission notice appear on all copies. * \****************************************************************************/ /* msh: meta-shell manager */ /* author: George Nelan */ #include #include #include #include #include #include #include extern int errno; #include "max.msh.h" #include "msh.h" #include "util.h" #define sigmask(m) (1 << ((m)-1)) /* everything for the new tty driver */ typedef struct { int f; struct sgttyb b; struct tchars c; int w; struct ltchars l; int d; } tty_t; /* one master... */ static int master_pid; static tty_t master_tty; /* a slave for each shell... */ typedef struct { int pid; /* == NIL => inactive */ tty_t pty; /* comm. link to master */ int nopolls; /* #times slave poll bypassed */ int maxnopolls; /* relative max #times poll bypassed */ char label; /* constant window label */ bool rs_pending; /* initial ReSize kludge */ char rs_tc[AMAXTCODE]; /* term code for ^ */ } slave_t; /* all slaves... */ static slave_t slave[AMAXSLAVES]; /* current input slave */ static slave_t *islave = 0; /* this must be explicitly initialized by a SS command */ /* current output window label */ static char olabel = '0'; /* result flag for shell creation (CC/CU) */ static bool C_result; /****************************************************************************/ #define read_master(chp) {while(get_master(chp) <= 0);} /****************************************************************************/ #define msg_master(msg) {put_master(msg,sizeof msg);} /****************************************************************************/ #define put_slave(sp,chp,len) {\ write(sp->pty.f,chp,len);\ } /****************************************************************************/ /* define current output window (can't use currency) */ #define goto_win(winchp) {\ olabel = (*winchp);\ put_master(W_WG,1);\ put_master(winchp,1);\ } /****************************************************************************/ public abort(msg) char *msg; { char str[128]; strcpy(str, msg); if (errno) { strcat(str, " possibly"); perror(str); } else puts(str); puts("\r"); res_m_tty(); exit(-1); } /****************************************************************************/ public master_driver() /* Poll master. * If input is there then see if it is an msh command. * If so, execute the command; * else send the input to the current slave. * Note: islave is 0 until explicitly inited via SS command. */ { int result; char ch0; reg char *ch0p = &ch0; char ch1; reg bool isacmd; if (get_master(ch0p) > 0) { isacmd = FALSE; if (*ch0p == M_CSI[0]) { read_master(&ch1); if (!(isacmd = (ch1 == M_CSI[1]))) { /* we consume M_CSI[0] */ *ch0p = ch1; } } if (isacmd) { read_master(ch0p); m_exec(*ch0p); } else if (islave) { put_slave(islave,ch0p,1); /* try to get echo asap */ islave->maxnopolls = (-1); } } } /* master_driver */ /****************************************************************************/ public bool isactive(sl) char sl; /* slave label */ { return (slave[sl - '1'].pid != NIL); } /* isactive */ /****************************************************************************/ /* slave cycle index for slave_driver */ static int slave_index = 0; /****************************************************************************/ public slave_driver() /* Poll slave 'slave_index'. Send any and all input thus received to the * remote window manager for output to its window. * Attempt to optimize io: if a slave has just output, give it higher * polling priority. * As long as no output is pending, keep giving it lower priority * (to a limit: MAXNOPOLLS). * This tends to delay output from quiet slaves, while active slaves are * tended to more quickly. Hopefully, total io is minimized, * thus decreasing echo response time. * Note the call to master_driver at start. This also tends to minimize * echo response time since input is tended to more often. */ { int result; reg slave_t *sp; char chb[AMAXCBUF]; reg char *chbp = chb; master_driver(); sp = &slave[slave_index]; /* bypass inactive slaves */ if (sp->pid == NIL) goto done; /* If the (active) slave has not been polled since * (maxnopolls) then go ahead and poll it now. */ if (sp->nopolls > sp->maxnopolls) { sp->nopolls = 0; if ((result = get_slave(sp,chbp,MAXCBUF)) > 0) { /* KLUDGE: If the slave has just been created, * now is the time to tell it about TERM */ if (sp->rs_pending) { slave_t *tsp; /* for do_RS */ tsp = islave; islave = sp; sp->rs_pending = FALSE; do_RS(sp->rs_tc); islave = tsp; } /* here is the output fragment packet header */ goto_win(&(sp->label)); put_master(chbp,result); /* Since we just got some input from the * slave, chances are we'll need some * more (asap). */ sp->maxnopolls = (-1); } else { /* limit the number of nopolls */ if ((sp->maxnopolls)++ > MAXNOPOLLS) sp->maxnopolls = MAXNOPOLLS; } } else (sp->nopolls)++; done: /* here is where we cycle for the next slave */ slave_index = ( (++slave_index) < MAXSLAVES ? slave_index : 0 ); } /* slave_driver */ /****************************************************************************/ /* execute msh cmd (assume valid!) */ static m_exec(ch) char ch; { if (ch == M_QX[0]) m_QX(); else if (ch == M_SS[0]) m_SS(); else if (ch == M_CC[0]) m_CC(); else if (ch == M_CU[0]) m_CU(); else if (ch == M_RS[0]) m_RS(); else if (ch == M_DS[0]) m_DS(); else if (ch == M_SX[0]) m_SX(); } /* m_exec */ /****************************************************************************/ /* quit execution */ static m_QX() { do_QX(); } /* m_QX */ static do_QX() { char sl; res_m_tty(); for (sl = '1'; sl <= MAXSLAVES + '0'; sl++ ) do_DS(sl); kill(0,SIGTERM); exit(0); } /* do_QX */ /****************************************************************************/ /* set new current shell to 'n' */ static m_SS() { char n; read_master(&n); do_SS(n); } /* m_SS */ static do_SS(n) char n; { islave = &slave[n - '1']; } /* do_SS */ /****************************************************************************/ /* TRUE if can create new current (csh) shell n code tc */ static m_CC() { char n,tc[AMAXTCODE]; short i; read_master(&n); i = 0; do { read_master(&tc[i]); } while (tc[i++] != ';' && i < MAXTCODE-1); tc[i] = '\0'; do_CC(n,tc); if (!C_result) msg_master("Sorry, shell not created...(ptys?)"); } /* m_CC */ static do_CC(n,tc) char n,*tc; { do_CU(n,tc,"/bin/csh"); } /* do_CC */ /****************************************************************************/ /* TRUE if can create new current (user) shell n code tc */ static m_CU() { char n,tc[AMAXTCODE]; char shell[AMAXSHELL]; short i; read_master(&n); i = 0; do { read_master(&tc[i]); } while (tc[i++] != ';' && i < MAXTCODE-1); tc[i] = '\0'; for (i = 0; i < MAXSHELL; i++) { read_master(&shell[i]); if (shell[i] == '\n') { shell[i] = '\0'; break; } } do_CU(n,tc,shell); if (!C_result) msg_master("Sorry, shell not created...(ptys?)"); } /* m_CU */ static do_CU(n,tc,shell) char n,*tc; char *shell; { int pid; int on; tty_t *pty; slave_t *sp; char tid[16]; /* the following code is pretty muchly standard pty to stdio stuff */ C_result = TRUE; sp = &slave[n - '1']; sp->nopolls = 0; sp->maxnopolls = (-1); pty = &(sp->pty); if (fail(ptmalloc(pty,tid))) return(C_result = FALSE); on = 1; ioctl(pty->f,FIONBIO,&on); set_s_pty(pty); if ((sp->pid = pid = vfork()) < 0) return(C_result = FALSE); else if (pid == 0) { /* slave */ tty_t tty_s; tty_t *tty = &tty_s; int t,cpid; close(pty->f); setpgrp(0,getpid()); if ((t = open("/dev/tty",O_RDWR)) >= 0) { ioctl(t,TIOCNOTTY,0); close(t); } close(2); if (fail(ptsalloc(tty,tid))) { msg_master("msh: bad pty slave alloc"); _exit(-1); } cpid = getpid(); setpgrp(0,cpid); ioctl(2,TIOCSPGRP,&cpid); close(0); close(1); dup(2); dup(2); set_s_tty(tty); execl(shell,shell,0); msg_master("msh: bad exec"); _exit(-1); } /* master */ do_SS(n); islave->rs_pending = TRUE; strcpy(islave->rs_tc,tc); } /* do_CU */ /****************************************************************************/ /* reset size of current slave */ /* we assume that the slave shell is in a state which can receive this */ static m_RS() { char tc[AMAXTCODE]; short i; i = 0; do { read_master(&tc[i]); } while (tc[i++] != ';' && i < MAXTCODE-1); tc[i] = '\0'; do_RS(tc); } /* m_RS */ static do_RS(tc) char *tc; { static char rs_msg[AMAXTCODE+16]; strcpy(rs_msg,"setenv TERM "); strcat(rs_msg,tc); rs_msg[strlen(rs_msg) - 1] = '\n'; /* wipe out ; terminator */ rs_msg[strlen(rs_msg) ] = '\0'; put_slave(islave,rs_msg,strlen(rs_msg)); } /* do_RS */ /****************************************************************************/ /* delete current shell, make n current */ static m_DS() { char n; read_master(&n); do_DS(n); } /* m_DS */ static do_DS(n) char n; { int gid; ioctl(islave->pty.f,TIOCGPGRP,&gid); /* I think this test solves the problem discussed in ../NOTES */ if (islave->pid != (-1) && gid != (-1)) { killpg(gid,SIGKILL); killpg(islave->pid,SIGKILL); } close(islave->pty.f); islave->pid = NIL; do_SS(n); } /* do_DS */ /****************************************************************************/ /* stop execution (bsd only) */ static m_SX() { char f; /* debug flag */ read_master(&f); do_SX(f); } /* m_SX */ static do_SX(f) char f; { if (f != '1') res_m_tty(); killpg(master_pid,SIGTSTP); set_m_tty(); } /* do_SX */ /****************************************************************************/ /* Return > 0 if ptyp ready, * else return <= 0 if not ready or error. * Note: if a shell has prematurely died on us, we will get an I/O error now. * So mark this slave as inactive. Admitedly this is a bit crude, but it * is simpler than waiting for child status deltas and it does seem to work. */ static int get_slave(sp,chp,len) reg slave_t *sp; reg char *chp; reg int len; { if (fail(len = read(sp->pty.f,chp,len)) && errno != EWOULDBLOCK) { len = (-1); sp->pid = NIL; msg_master("Shell has died..."); } return(len); } /* get_slave */ /****************************************************************************/ /* Allocate master side of psuedo-terminal pair (return 1 else -1 if fail) */ static int ptmalloc(ptyp,name) tty_t *ptyp; char *name; { static char pnmap[] = "0123456789abcdef"; static char ptypn[] = "/dev/pty??"; int i,j; int p; for (i = 'p'; i <= 's'; i++) { ptypn[8] = (char) i; for (j = 0; j <= 15; j++) { ptypn[9] = pnmap[j]; if (!fail(p = open(ptypn,O_RDWR,0700))) goto openok; } } return(-1); openok: ptyp->f = p; strcpy(name,ptypn); return(1); } /* ptalloc */ /****************************************************************************/ /* Allocate slave side of psuedo-terminal pair (return 1 else -1 if fail) */ static int ptsalloc(ttyp,name) tty_t *ttyp; char *name; { int t; name[strlen("/dev/")] = 't'; if (fail(t = open(name,O_RDWR,0700))) return(-1); ttyp->f = t; return(1); } /* ptalloc */ /*****************************************************************************/ /* Original flags for tty state stuff */ static int o_flags; /*****************************************************************************/ /* Save original master tty state */ sav_m_tty() { get_state(master_tty.f,&master_tty); /* save original flags */ o_flags = master_tty.b.sg_flags; /* make sure tabs are space-converted! */ master_tty.b.sg_flags |= XTABS; } /* sav_m_tty */ /*****************************************************************************/ /* Reset master tty to original state */ res_m_tty() { /* restore original flags */ master_tty.b.sg_flags = o_flags; set_state(master_tty.f,&master_tty); } /* res_t_tty */ /*****************************************************************************/ /* Set master tty to new state */ set_m_tty() { tty_t news; news.b.sg_ispeed = master_tty.b.sg_ispeed; news.b.sg_ospeed = master_tty.b.sg_ospeed; news.b.sg_erase = (-1); news.b.sg_kill = (-1); news.b.sg_flags = master_tty.b.sg_flags; news.b.sg_flags |= XTABS; news.b.sg_flags &= (~CRMOD); news.b.sg_flags &= (~ECHO); news.b.sg_flags |= CBREAK; news.c.t_intrc = (-1); news.c.t_quitc = (-1); news.c.t_startc = master_tty.c.t_startc; news.c.t_stopc = master_tty.c.t_stopc; news.c.t_eofc = (-1); news.c.t_brkc = (-1); news.w = master_tty.w; news.l.t_suspc = (-1); news.l.t_dsuspc = (-1); news.l.t_rprntc = (-1); news.l.t_flushc = (-1); news.l.t_werasc = (-1); news.l.t_lnextc = (-1); news.d = master_tty.d; set_state(master_tty.f,&news); } /* set_m_tty */ /*****************************************************************************/ /* Set slave tty to original state of master (may not be necessary) */ set_s_tty(ttyp) tty_t *ttyp; { set_state(ttyp->f,&master_tty); } /* set_s_tty */ /*****************************************************************************/ /* Set slave pty to original state of master */ set_s_pty(ptyp) tty_t *ptyp; { set_state(ptyp->f,&master_tty); } /* set_s_pty */ /*****************************************************************************/ /* Get state of f into ttyp */ static get_state(f,ttyp) int f; tty_t *ttyp; { ioctl(f,TIOCGETP,&(ttyp->b)); ioctl(f,TIOCGETC,&(ttyp->c)); ioctl(f,TIOCLGET,&(ttyp->w)); ioctl(f,TIOCGLTC,&(ttyp->l)); ioctl(f,TIOCGETD,&(ttyp->d)); } /* get_state */ /*****************************************************************************/ /* Set state of f from ttyp */ static set_state(f,ttyp) int f; tty_t *ttyp; { ioctl(f,TIOCSETP,&(ttyp->b)); ioctl(f,TIOCSETC,&(ttyp->c)); ioctl(f,TIOCLSET,&(ttyp->w)); ioctl(f,TIOCSLTC,&(ttyp->l)); ioctl(f,TIOCGETD,&(ttyp->d)); } /* set_state */ /****************************************************************************/ /* initialize... */ public init_msh() { int i; char *w; /* must be interactive */ if (!isatty()) { puts("Sorry, msh requires interactive control."); exit(-1); } /* may be bad for root to do */ if (geteuid() == 0) { puts("Sorry root, this program may be unsafe for you to run."); exit(-1); } /* disable SIGTTIN,SIGTTOU for self & descendents */ sigblock(sigmask(SIGTTIN)); sigblock(sigmask(SIGTTOU)); master_pid = getpid(); master_tty.f = STDOUT; sav_m_tty(); set_m_tty(); /* for CBREAK mode, ignore => intr */ signal(SIGINT,SIG_IGN); for (i = 0; i < MAXSLAVES; i++) { slave[i].pid = NIL; slave[i].label = (char)(i + '1'); slave[i].rs_pending = FALSE; } } /* init_msh */ @@ END-OF-FILE echo x - release/source/msh/msh.h ln -s ../../public/wms/msh.h release/source/msh/msh.h echo x - release/source/msh/util.h ln -s ../util.h release/source/msh/util.h echo done Brought to you by Super Global Mega Corp .com