4.2 Window Management System (wms part 3 of 4)

Marc Lesure system at asuvax.UUCP
Fri Sep 20 15:39:05 AEST 1985


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 at asu
ARPA:   lesure%asu at csnet-relay

------------------------------------<cut here>-------------------------
# This is a shell archive.  Remove all lines before this one.
# Use 'sh <this file>' 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 <stdio.h>

extern	init_msh();
extern	slave_driver();

#include <sys/ioctl.h>


/****************************************************************************/
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 <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <signal.h>
#include <sgtty.h>
#include <errno.h>
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 <break> => 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



More information about the Comp.sources.unix mailing list