[net.emacs] guido mchan fix

purtilo@uiuccsb.UUCP (08/07/83)

#N:uiuccsb:7400001:000:41198
uiuccsb!purtilo    Aug  6 18:00:00 1983

	Date: 2 Aug 83 10:24:00 (DT) (Tue)
	From: mcvax!guido (Guido van Rossum)
	Subject: Re:  emacs fix in 4.1c
	Message-Id: <8308020824.AA11799@MCVAX.UUCP>
	Received: by MCVAX.UUCP (3.327/3.14)
		id AA11799; 2 Aug 83 10:24:00 (DT) (Tue)
	To: philabs!uiucdcs!uiuccsb!purtilo
	Cc: decvax!pegasus!hansen
	
	Jim,
	Could you please post the fixes to "mchan.h" to net.emacs?
	In Europe we don't receive that group, and consequently can't post to
	it either.  Thanks in advance,
		Guido van Rossum, Math. Centre, Amsterdam
		...!{decvax,philabs}!mcvax!guido



Done ... what follows is "mchan.c" then "mchan.h". When I added it locally
I ifdef'd it so that by not defining "uiuc" you got the original mchan,
more or less. You should either define uiuc in your config.h or just edit
out all the conditionals appropriately below. Thanks to Guido.

We found it convenient to hack "dsp.c" slightly as well. Our version is
appended as well. Change is basically for folks who invoke an emacs
over an rlogin, sends a null back to propagate ioctl's to the local
tty driver.

						Jim Purtilo
						...!uiucdcs!purtilo

/********************** mchan.c *************************/

/* These procedures hope to give EMACS a facility for dealing with multiple
   processes in a reasonable way */

/*		Copyright (c) 1981 Carl Ebeling		*/

/* Modified 8-Sept-81 Jeffrey Mogul (JCM) at Stanford
 *	- removed RstDsp() after failure to open mpx file; this
 *	  was leading to nasty infinite loop.  Probably this should
 *	  be done better and in other situations as well.
 *
 * Modified 20-Jul-83 Guido van Rossum at Mathematical Centre, Amsterdam
 *  - replaced multiplexed file stuff to use 4.1c pseudo-terminals,
 *    see pty(4).
 * These modifications (c) 1983 Mathematical Centre, Amsterdam.
 *
 * Guido's mods added (with thanks) with only slight modification,
 *     28-Jul-83 James Purtilo, University of Illinois at Urbana-Champaign. 
 */


#include "config.h"
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <wait.h>
#include <sgtty.h>

#ifndef uiuc
#include <sys/mpx.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>


#include "window.h"
#include "keyboard.h"
#include "buffer.h"
#include "mlisp.h"
#include "macros.h"

#ifdef subprocesses
#include "mchan.h"
#else
extern int errno;
#endif

#ifdef subprocesses

#define ChunkSize 500		/* amount to truncate when buffer overflows */
static int ProcessBufferSize;   /* Maximum size for process buffer */

/* ioans_rec is a structure used to return the IOANS message to a sender 
   process. The structure is initialized in InitMpx() by gtty on the 
   terminal. */
struct w_msg {
    short   code;
    struct sgttyb   ioctl_ans;
#ifdef uiuc
}                          iopg_rec, ioany_rec;
#else
}               ioans_rec, iopg_rec, ioany_rec;
#endif

#ifndef uiuc
struct wh   ioans;		/* The record actually used to send
				   IOANS_REC 
                                   not any more ... purtilo */
#endif

struct wh   iopg;               /* The record actually used to send
                                   IOPG_REC */
struct wh   ioany;

char mpx_filename[50];		/* Name of Multiplexed file */

int     mpx_fd;			/* Multiplexed file */
#ifdef ce
FILE *err_file;	/* file to which we will dump all unexpected stuff */
#endif
int     err_id;			/* User's ID for error messages */

struct channel_blk  stdin_chan;
int     child_changed;		/* Flag when a child process has ceased
				   to be */
struct VariableName *MPX_process;
struct channel_blk *MPX_chan;


char   *SIG_names[] =    /* this list needs updating ... purtilo */
{
    "",
    "Hanged",
    "Interrupted",
    "Quit*",
    "Illegal instruction*",
    "Trapped*",
    "Iot*",
    "EMT*",
    "FPE*",
    "Killed",
    "Bus error*",
    "Segment violation*",
    "Sys call error*",
    "Pipe write error",
    "Alarm clock",
    "Termination",
    "",
    "Stopped",
    "Stopped",
    "", "", "", "", "", "", ""
};

/* Start up a subprocess with its standard input and output connected to 
   a channel on the mpx file.  Also set its process group so we can kill it
   and set up its process block.  The process block is assumed to be pointed
   to by current_process
*/
create_process (command)
register char  *command;
{
    register    channel,
                newfd,
                pid,
                i;
    extern char *shell ();

#ifndef uiuc

    if ((channel = chan (mpx_fd)) == -1) {
	error ("Can't connect subchannel\n");
	return - 1;
    }

    newfd = extract (channel, mpx_fd);

#else

#define STRLEN(x) (sizeof(x)-1)

    {
	char line[50]; /* Name for pseudo tty device */
	char c;
	int i;

	/* Find an available pseudo terminal.
	   (Code adapted from rlogind.)
	   We use the variable names from the mpx code to keep changes
	   as few as possible: `channel' is the master side, `newfd' is
	   the slave side of the pseudo terminal. */

	for (c = 'p'; c <= 's'; c++) {
		struct stat stb;
		strcpy(line, "/dev/ptyXX");
		line[STRLEN("/dev/pty")] = c;
		line[STRLEN("/dev/ptyp")] = '0';
		if (stat(line, &stb) < 0)
			break;
		for (i = 0; i < 16; i++) {
			line[STRLEN("/dev/ptyp")] = "0123456789abcdef"[i];
			channel = open(line, 2);
			if (channel >= 0) {
				line[STRLEN("/dev/")] = 't';
				newfd = open(line, 2);
 				if (newfd < 0) {
					close(channel);
					line[STRLEN("/dev/")] = 'p';
				}
				else
					goto gotpty; /* Multi-level break */
			}
		}
	}
	error("No pseudo tty available");
	return - 1;

gotpty:
	{
	    int yes = 1;
	    ioctl(channel, TIOCREMOTE, &yes);
	    ioctl(channel, FIONBIO, &yes); /* do not block on writes */
	}
    }

#endif    /* done with uiuc insertions */

    if ((pid = vfork ()) < 0) {
	error ("Fork failed");
#ifdef uiuc
	close(channel);
#else
	detach (channel, mpx_fd);
#endif
	close (newfd);
	return - 1;
    }

    if (pid == 0) {
	sigrelse (SIGCHLD);
	setpgrp (0, getpid ());
	sigsys (SIGINT, SIG_DFL);
	sigsys (SIGQUIT, SIG_DFL);
	close (0);
	dup (newfd);
	close (1);
	dup (newfd);
	close (2);
	dup (newfd);
#ifdef uiuc
	close (newfd);
	for (newfd = 3; newfd < 20; ++newfd)
		close(newfd);
#endif
	execlp (shell (), shell (), "-c", command, 0);
	write (1, "Couldn't exec the shell\n", 24);
	_exit (1);
    }

    current_process -> p_name = command;
    current_process -> p_id = pid;
    current_process -> p_flag = RUNNING;
    current_process -> p_chan.ch_index = channel;
    current_process -> p_chan.ch_ptr = NULL;
    current_process -> p_chan.ch_count = 0;
    current_process -> p_chan.ch_outrec.index = channel;
    current_process -> p_chan.ch_outrec.count = 0;
    current_process -> p_chan.ch_outrec.ccount = 0;
    close (newfd);  
    return 0;
}
#endif

/* Process a signal from a child process and make the appropriate change in 
   the process block. Since signals are NOT queued, if two signals are
   received before this routine gets called, then only the first process in
   the process list will be handled.  We will try to get the MPX file stuff
   to help us out since it passes along signals from subprocesses.
*/
int     subproc_id;		/* The process id of a subprocess
				   started by the old subproc stuff.
				   We will zero it so they will know it
				   has finished */
child_sig () {
    register int    pid;
    union wait w;
    register struct process_blk *p;
    extern struct process_blk  *get_next_process ();

loop: 

    pid = wait3 (&w.w_status, WUNTRACED | WNOHANG, 0);
    if (pid <= 0) {
	if (errno == EINTR) {
	    errno = 0;
	    goto loop;
	}
#ifdef subprocesses
	if (pid == -1) {
	    if (!active_process (current_process))
		current_process = get_next_process ();
	}
#endif
	return;
    }
    if (pid == subproc_id) {	/* It may not be our progeny */
	subproc_id = 0;		/* Take care of those subprocesses first 
				*/
	goto loop;
    }
#ifndef subprocesses
    goto loop;
}
#else
    for (p = process_list; p != NULL; p = p -> next_process)
	if (pid == p -> p_id)
	    break;
    if (p == NULL)
	goto loop;		/* We don't know who this is */

    if (WIFSTOPPED (w)) {
	p -> p_flag = STOPPED;
	p -> p_reason = w.w_stopsig;
    }
    else
	if (WIFEXITED (w)) {
	    p -> p_flag = EXITED | CHANGED;
	    child_changed++;
	    p -> p_reason = w.w_retcode;
	}
	else
	    if (WIFSIGNALED (w)) {
		p -> p_flag = SIGNALED | CHANGED;
		child_changed++;
		p -> p_reason = w.w_termsig;
	    }
    if (!active_process (current_process))
	current_process = get_next_process ();
    goto loop;
}

/* Look through channel blocks to find the matching the channel index */
/* There should probably be a short vector crossreferencing channel to process
   so these look-ups are not quite so stupid (ce) */

struct channel_blk *find_channel (index)
register int   index;
{
    register struct process_blk *p;

    if (index == mpxin -> ch_index)
	return (mpxin);
    for (p = process_list; p != NULL; p = p -> next_process)
	if (index == p -> p_chan.ch_index)
	    return (&p -> p_chan);
    return (NULL);
}

/* Find the corresponding process for a pointer to a channel block.
*/
struct process_blk *index_to_process (index)
register int   index;
{
    register struct process_blk *p;

    if (index == mpxin -> ch_index)
	return (NULL);
    for (p = process_list; p != NULL; p = p -> next_process) {
	if (!active_process (p))
	    continue;
	if (p -> p_chan.ch_index == index)
	    break;
    }
    return (p);
}

/* Find the process which is connected to buf_name */
struct process_blk *find_process (buf_name)
register char   *buf_name;
{
    register struct process_blk *p;

    if (buf_name == NULL)
	return (NULL);
    for (p = process_list; p != NULL; p = p -> next_process) {
	if (!active_process (p))
	    continue;
	if (strcmp (p -> p_chan.ch_buffer -> b_name, buf_name) == 0)
	    break;
    }
    return (p);
}

/* Get the first active process in the process list and assign to the current
   process
*/
struct process_blk *get_next_process () {
    register struct process_blk *p;

    for (p = process_list; p != NULL && !active_process (p); p = p -> next_process);
    return (p);
}

/* This corresponds to the filbuf routine used by getchar.  This handles all 
   the input from the mpx file.  Input coming from the terminal is sent back
   to getchar() in the same manner as filbuf.  Control messages are sent to
   Take_msg for interpretation.  Normal input from other channels is routed
   to the correct buffer
*/
static int mpx_share;		/* channel index for someone sharing stdin */
static char cbuffer[BUFSIZ];	/* used for reading mpx file */
static int  mpx_count;		/* number of unprocessed characters in
				   buffer */
static struct rh   *MXP;	/* pointer into buffer of records */

#ifdef uiuc
fill_chan (chan)
register struct channel_blk *chan;
{
    extern struct channel_blk *find_channel();
    register struct channel_blk *this_channel;
    register struct process_blk *p;
    register int nfds;
    register int fd;
    int mpx_count;
    int readfds;
    int writefds;

readloop:
    /* weed out dead processes before going to sleep */
    if (child_changed) {
	child_changed = 0;
	change_msgs ();
    }
    readfds = 1; /* Set bit for stdin */
    writefds = 0;
    for (p = process_list; p != NULL; p = p->next_process) {
    	if (active_process (p)) {
	    readfds |= 1 << (p -> p_chan.ch_index);
	    if (p -> p_chan.ch_outrec.count || p -> p_chan.ch_outrec.ccount)
	    	writefds |= 1 << (p -> p_chan.ch_outrec.index);
	}
    }
    errno = 0;
    nfds = select (20, &readfds, &writefds, (int*)0, (int*)0);
    	/* This blocks until input is available somewhere */

    if (nfds < 0) {
    	if (errno == EINTR)
	    goto readloop;
	error ("Select system call failed");
	DoDsp(1);
	return EOF;
    }
    for (fd = 0; fd < 20; ++fd) {
	/* Note - this order serves stdin first,
	   which is desirable (think of a sub-
	   process flooding us with data while
	   the user wants to continue editing). */
	if (writefds & (1<<fd)) { /* Retry pending output on fd */
	    for (p = process_list; p != NULL; p = p -> next_process) {
		if (active_process (p) &&
			p -> p_chan.ch_outrec.index == fd) {
		    send_chan (p);
		    break;
		}
	    }
	    if (p == NULL) {
		/* process must have died in the meantime */
		goto readloop;
	    }
	}
	if (readfds & (1<<fd)) { /* Input available on fd */
	loop:
	    errno = 0;
	    mpx_count = read (fd, cbuffer, sizeof cbuffer);
	    if (mpx_count <= 0) {
		if (errno == EINTR)
		    goto loop;
		error ("Failed to read ready descriptor");
		DoDsp(1);
		return EOF;
	    }
	    if (fd == 0) /* This is stdin */
		this_channel = mpxin;
	    else {
	        this_channel = find_channel (fd);
		if (this_channel == NULL) {
		    /* probably a process that just died */
		    goto readloop;
		}
	    }
	    this_channel -> ch_ptr = cbuffer;
	    this_channel -> ch_count = mpx_count;
	    if (this_channel == mpxin) {
		mpxin -> ch_count--;
		return (*mpxin -> ch_ptr++ & 0377);
	    }
	    else
		stuff_buffer (this_channel);
	}
    }
    goto readloop;
}

#else

fill_chan (chan)
register struct channel_blk *chan;
{
    extern int  mpx_fd;		/* mpx file number */
    extern struct channel_blk  *find_channel ();

    register struct mpx_msg *msg;
    register int    record_size,
                    msg_length;
    register struct channel_blk *this_channel;

readloop: 

/* Temporary hack until unblocking is fixed up: retry pending output whenever
   anything is read.
*/
    {
	register struct process_blk *p;
	for (p = process_list; p != NULL; p = p -> next_process)
	    if (active_process (p) &&
		    (p -> p_chan.ch_outrec.count || p -> p_chan.ch_outrec.ccount))
		send_chan (p);
    }

    if (mpx_count == 0) {
	mpx_count = read (mpx_fd, cbuffer, BUFSIZ);
	if (mpx_count <= 0)
	    return (EOF);
	MXP = (struct rh   *) cbuffer;
    }

    if (child_changed) {
	child_changed = 0;
	change_msgs ();
    }

    while (mpx_count > 0) {
	record_size = (MXP -> count + MXP -> ccount + 7) & 0XFFFE;
	if (MXP -> count == 0) {/* process a mpx channel record msg */
	    for (msg = (struct mpx_msg *) (MXP + 1); MXP -> ccount > 0;) {
		msg_length = Take_msg (msg, MXP -> index);
		msg = (struct mpx_msg  *) ((int) msg + msg_length);
		MXP -> ccount -= msg_length;
	    }
	    mpx_count -= record_size;
	    MXP = (struct rh   *) ((int) MXP + record_size);
	}
	else {
	    if (MXP -> index == mpx_share) this_channel = mpxin;
	    else if ((this_channel = find_channel (MXP -> index)) == NULL) {
			error ("Illegal channel index");
		 	return (EOF);
	    }
	    this_channel -> ch_ptr = (char *) (MXP + 1);
	    this_channel -> ch_count = MXP -> count;
	    mpx_count -= record_size;
	    MXP = (struct rh   *) ((int) MXP + record_size);

	/* input from TTY comes through the distinguished channel block 
	*/
	    if (this_channel == mpxin) {
		mpxin -> ch_count--;
		return (*mpxin -> ch_ptr++ & 0377);
	    }
	    else
		stuff_buffer (this_channel);
	}
    }
    goto readloop;
}
#endif   /* fill_chan change.... purtilo */


/* Take a channel message and do the right thing with it:
        IOCTL -> return appropriate IOANS
	EOT   -> ignore (spurious & bogus EOT's seem to thrive !!
	CLOSE -> close the channel
	anything else-> print msg and ignore
*/
#ifndef uiuc
Take_msg (msg, index)
register struct mpx_msg *msg;
{
    extern  mpx_fd;
    register struct process_blk *p;

    switch (msg -> mpx_code & 0377) {
        case M_IOCTL: 
            if (msg -> mpx_arg == TIOCGETP) {
                ioans.index = index;
                if (write (mpx_fd, &ioans, sizeof (ioans)) != sizeof (ioans))
                    error ("Unable to reply to process IOCTL");
            }
            else if (msg -> mpx_arg == TIOCGPGRP) {
                /* In response to TIOCGPGRP return an "error"
                 * record.  This is  so csh will get the "right"
                 * answer to its TIOCGPGRP.
                 */
                iopg.index = index;
                if (write (mpx_fd, &iopg, sizeof (iopg)) != sizeof (iopg))
                    error ("Unable to reply to process IOCTL");
            } else {
                /* In response to anything else, reflect the same data
                 * back.
                 */
                ioany.index = index;
                ioany_rec.ioctl_ans = msg -> mpx_ioctl;
                if (write (mpx_fd, &ioany, sizeof (ioany)) != sizeof (ioany))
                    error ("Unable to reply to process IOCTL");
            }

	    return (4 + sizeof (struct sgttyb));

	/* We get a WATCH message when someone opens our multiplexed file. If
	   we do an attach then they can connect.  Keep track of the shared
	   input by saving the index in share. (For now only one shared open)
	*/
	case M_WATCH:
#ifdef ce
	    fprintf(err_file, "Watch: %d\n", index); 
#endif
	    if ((mpx_share == -1) && sflag)
	        { mpx_share = index;/* Save the index of the shared channel */
		  attach(index, mpx_fd);/* And attach... */
		  message ("Connecting a shared channel");
		  DoDsp(1);
		}
	      else
	        { detach(index, mpx_fd);/* Do not allow the open */
		  message ("Blocking shared open");
		  DoDsp(1);
		}

	    return(4);

	case M_UBLK: 
	    if ((p = index_to_process (index)) == NULL) {
#ifdef ce
	     fprintf(err_file, "%d: Msg code:%d, arg:%d, index:%d\n",
	       err_id, msg->mpx_code, msg->mpx_arg, index); 
#endif
		return (4);
	    }
	    message ("Unblocking");
	    send_chan (p);
	    return (4);

	case M_EOT: 
	 /* if (index != mpxin->ch_index) 
	      fprintf(err_file, "%d: Msg code:%d, arg:%d, index:%d\n", 
	      		err_id, msg->mpx_code, msg->mpx_arg, index); 

	  if (index == mpx_share)
	        {detach(index, mpx_fd);
	         message ("Disconnecting shared channel");
		 DoDsp(1);
		}
	*/
	   return (4);				/* ignore ! */
	case M_CLOSE: 
#ifdef ce
	    fprintf(err_file, "Close: %d\n", index); 
#endif
	    detach (index, mpx_fd);		/* reuse channel */
	    if (index == mpx_share)
	    	{ message ("Disconnecting shared channel");
	    	  mpx_share = -1;
		}
	    return (4);
	case M_SIG:
#ifdef ce
	    fprintf(err_file, "SIG: %d\n", index);  
#endif
	    return (4);
	case M_BLK:
#ifdef ce
	    fprintf(err_file, "Blocking: %d\n", index);  
#endif
	    return (4);
	case M_OPEN:
#ifdef ce
	    fprintf(err_file, "Opening: %d\n", index);
#endif
	    return (4);
	default: 
#ifdef ce
	    fprintf(err_file, "%d: Msg code:%d, arg:%d, index:%d\n",
	      err_id, msg->mpx_code, msg->mpx_arg, index);
#endif

	    return (4);
    }
}
#endif


/* Give a message that a process has changed and indicate why.  Dead processes
   are not removed until after a Display Processes command has been issued so
   that the user doesn't wonder where his process went in times of intense
   hacking
*/
change_msgs () {
    register struct process_blk *p;
    register struct buffer *old = bf_cur;
    char    line[50];

    for (p = process_list; p != NULL; p = p -> next_process)
	if (p -> p_flag & CHANGED) {
	    p -> p_flag &= ~CHANGED;
	    switch (p -> p_flag & (SIGNALED | EXITED)) {
		case SIGNALED: 
		    SetBfp (p -> p_chan.ch_buffer);
		    SetDot (bf_s1 + bf_s2 + 1);
		    sprintfl (line, sizeof line, "%s\n", SIG_names[p -> p_reason]);
		    InsStr (line);
#ifdef uiuc
		    close (p -> p_chan.ch_index);
		    p -> p_chan.ch_index = -1;
#endif
		    break;
		case EXITED: 
		    SetBfp (p -> p_chan.ch_buffer);
		    SetDot (bf_s1 + bf_s2 + 1);
		    sprintfl (line, sizeof line, "Exited%s\n",
			    (p -> p_reason == 0 ? "" : " Abnormally"));
		    InsStr (line);
#ifdef uiuc
		    close (p -> p_chan.ch_index);
		    p -> p_chan.ch_index = -1;
#endif
		    break;
	    }
	}
    DoDsp (1);
    SetBfp (old);
}

/* Send any pending output as indicated in the process block to the 
   appropriate channel.
*/
send_chan (process)
register struct process_blk *process;
{
    register struct wh *output;

    output = &process -> p_chan.ch_outrec;
    if (output -> count == 0 && output -> ccount == 0) {
       	/* error ("Null output"); */
	return 0;		/* No output to be done */
    }
#ifdef uiuc
    if (write (output -> index, output -> data, output -> count)
	!= output -> count){
	    if(errno == EWOULDBLOCK){
		/* message ("Blocking")*/ ;
	    }
    }
#else
    if (write (mpx_fd, output, sizeof (*output)) != sizeof (*output))
	/* message ("Blocking")*/ ;
#endif
    else {
	output -> count = 0;
	output -> ccount = 0;
    }
}

/* Output has been recieved from a process on "chan" and should be stuffed in
   the correct buffer
*/
stuff_buffer (chan)
register struct channel_blk *chan;
{
    struct buffer  *old_buffer = bf_cur;
    static int  lockout;	/* lockout other stuffs if one is in
				   progress */

    if (lockout) {
	error ("On-output procedure asked for input: you lose big!!!");
	return - 1;
    }
    else
	lockout = 1;
    if (chan -> ch_proc == NULL) {
	SetBfp (chan -> ch_buffer);
	SetDot (bf_s1 + bf_s2 + 1);
	InsCStr (chan -> ch_ptr, chan -> ch_count);
	if ((bf_s1 + bf_s2) > ProcessBufferSize) {
	    DelFrwd (1, ChunkSize);
	    DotLeft (ChunkSize);
	}
	if (bf_cur -> b_mark == NULL)
	    bf_cur -> b_mark = NewMark ();
	SetMark (bf_cur -> b_mark, bf_cur, dot);
	DoDsp (1);
	SetBfp (old_buffer);
	if (interactive)
	    WindowOn (bf_cur);
    }
    else {
	MPX_process -> v_binding -> b_exp -> exp_int =
	    strlen (chan -> ch_buffer -> b_name);
	MPX_process -> v_binding -> b_exp -> exp_v.v_string =
	    chan -> ch_buffer -> b_name;
	MPX_chan = chan;	/* User will be able to get the output
				   for */
	ExecuteBound (chan -> ch_proc);
	MPX_chan = NULL;	/* a very short time only */
	SetBfp (chan -> ch_buffer);
	if ((bf_s1 + bf_s2) > ProcessBufferSize) {
	    DelFrwd (1, ChunkSize);
	    DotLeft (ChunkSize);
	}
	SetBfp (old_buffer);
    }
    chan -> ch_count = 0;
    lockout = 0;
}

/* Return a count of all active processes
*/
count_processes () {
    register struct process_blk *p;
    register    count = 0;

    for (p = process_list; p != NULL; p = p -> next_process)
	if (active_process (p))
	    count++;
    return (count);
}
/* Flush a process but only if process is inactive
*/
flush_process (process)
register struct process_blk *process;
{
    register struct process_blk *p,
                               *lp;

    if (active_process (process)) {
	error ("Can't flush an active process");
	return 0;
    }

    for (lp = NULL, p = process_list;
	    (p != NULL) && (p != process);
	    lp = p, p = p -> next_process);
    if (p != process) {
	error ("Can't find process");
	return 0;
    }
    if (lp == NULL)
	process_list = process -> next_process;
    else
	lp -> next_process = process -> next_process;
    free (process);
    return 0;
}

/* Kill off all active processes: usually done only to exit when user really
   insists
*/
kill_processes () {
    register struct process_blk *p;

    for (p = process_list; p != NULL; p = p -> next_process)
	if (active_process (p))
	    killpg (p -> p_id, SIGKILL);
#ifndef uiuc
    detach (mpxin -> ch_index, mpx_fd);
#endif
}

/* Start up a new process by creating the process block and initializing 
   things correctly
*/
start_process (com, buf, proc)
register char  *com,
               *buf;
{
    extern struct process_blk  *get_next_process ();

    if (com == 0)
	return (0);
    current_process =
	(struct process_blk *) malloc (sizeof (struct process_blk));
    if (current_process == NULL) {
	error ("Out of memory");
	return 0;
    }
#ifdef uiuc
    current_process -> p_chan.ch_index = -1; /* So close won't do harm */
    current_process -> p_flag = 0; /* Let no-one think we're active */
	/* This last line took us more than a day to come up with! */
#endif
    sighold (SIGCHLD);
    current_process -> next_process = process_list;
    process_list = current_process;
    if (create_process (com) < 0) {/* job was not started, so undo */
	flush_process (current_process);
	current_process = get_next_process ();
	sigrelse (SIGCHLD);
	return 0;
    }
    SetBfn (buf == NULL ? "Command execution" : buf);
    if (interactive)
	WindowOn (bf_cur);
    current_process -> p_chan.ch_buffer = bf_cur;
    current_process -> p_chan.ch_proc = (proc < 0 ? NULL : MacBodies[proc]);
    sigrelse (SIGCHLD);
    return 0;
}

/* Emacs command to start up a default process: uses "Command Execution"
   buffer if one is not specified.  Also does default stuffing
*/
StartProcess () {
register char   *com = (char *) (savestr (getstr ("Command: ")));
register char   *buf;

    if ((com == 0) || (*com == 0)) {
	error ("No command");
	return 0;
    }
    buf = (char *) getstr ("Connect to buffer: ");
    if (*buf == 0)
	buf = NULL;
    start_process (com, buf, -1);
}

/* Start up a process whose output will get filtered through a procedure
   specified by the user
*/
StartFilteredProcess () {
register char   *com = (char *) (savestr (getstr ("Command: ")));
register char   *buf;
    int     proc;
    char bufname[200];

    if ((com == 0) || (*com == 0)) {
	error ("No command");
	return 0;
    }
    buf = getstr ("Connect to buffer: ");
    if (buf==0) return 0;
    strcpy (bufname, buf);
    proc = getword (MacNames, "On-output procedure: ");
    start_process (com, bufname[0] ? bufname : NULL, proc);
    return 0;
}

/* Insert a filter-procedure between a process and emacs. This function
   should subsume the StartFilteredProcess function, but we should retain
   that one for compatibility I suppose... */
InsertFilter ()
{
    register char  *proc_name = getstr ("Process: ");
    register struct process_blk *process;
    register int    proc;

    if ((process = find_process (proc_name)) == NULL) {
	error ("Process does not exist");
	return 0;
    }
    proc = getword (MacNames, "On-output procedure: ");
    process -> p_chan.ch_proc = (proc < 0 ? NULL : MacBodies[proc]);
    return (0);
}

/* Reset filter rebinds the process filter to NULL */
ResetFilter () {
    register char  *proc_name = getstr ("Process: ");
    register struct process_blk *process;

    if ((process = find_process (proc_name)) == NULL) {
	error ("Process does not exist");
	return 0;
    }
    process -> p_chan.ch_proc = NULL;
    return 0;
}

/* ProcessFilterName returns the name of the process filter */
ProcessFilterName () {
    register char  *proc_name = getstr ("Process: ");
    register struct process_blk *process;
    char   *name;
    if ((process = find_process (proc_name)) == NULL) {
	error ("Process does not exist");
	return 0;
    }
    MLvalue -> exp_type = IsString;
    MLvalue -> exp_release = 0;
    name = process -> p_chan.ch_proc
	? process -> p_chan.ch_proc -> b_name : "";
    MLvalue -> exp_int = strlen (name);
    MLvalue -> exp_v.v_string = name;
    return 0;
}

/* List the current processes.  After listing stopped or exited processes,
   flush them from the process list.
*/
ListProcesses () {
    register struct buffer *old = bf_cur;
    register struct process_blk *p;
    char    line[150];

    SetBfn ("Process list");
    if (interactive)
	WindowOn (bf_cur);
    EraseBf (bf_cur);
    InsStr ("\
Buffer			Status		   Command\n\
------			------		   -------\n");
    sighold (SIGCHLD);
    for (p = process_list; p != NULL; p = p -> next_process) {
	sprintfl (line, sizeof line, "%-24s", p -> p_chan.ch_buffer -> b_name);
	InsStr (line);
	switch (p -> p_flag & (STOPPED | RUNNING | EXITED | SIGNALED)) {
	    case STOPPED: 
		sprintfl (line, sizeof line, "%-17s", "Stopped");
		break;
	    case RUNNING: 
		sprintfl (line, sizeof line, "%-17s", "Running");
		break;
	    case EXITED: 
		sprintfl (line, sizeof line, "Exited %-10s",
			(p -> p_reason == 0 ? "" : "Abnormally"));
		flush_process (p);
		break;
	    case SIGNALED: 
		sprintfl (line, sizeof line, "%-17s", SIG_names[p -> p_reason]);
		flush_process (p);
		break;
	    default: 
		continue;
	}
	InsStr (line);
	sprintfl (line, sizeof line, "   %-32s\n", p -> p_name);
	InsStr (line);
    }
    sigrelse (SIGCHLD);
    bf_modified = 0;
    SetBfp (old);
    WindowOn (bf_cur);
    return 0;
}

/* Take input from mark to dot and feed to the subprocess */
RegionToProcess () {
    register    left,
                right;
    register char  *proc_name = getstr ("To process: ");
    register struct process_blk *process;
    register struct wh *output;

    if ((process = find_process (proc_name)) == NULL) {
	error ("Process does not exist");
	return 0;
    }

    if (bf_cur -> b_mark == 0) {
	error ("Mark not set");
	return 0;
    }
    left = ToMark (bf_cur -> b_mark);
    if (left <= dot)
	right = dot;
    else {
	right = left;
	left = dot;
    }
    if (right - left <= 0) {
	error ("Region is null");
	return 0;
    }
    if (left < bf_s1 && right >= bf_s1)
	GapTo (left);
    output = &process -> p_chan.ch_outrec;
    if (output -> count || output -> ccount)
	error ("Overwriting data on blocked channel");
    output -> index = process -> p_chan.ch_index;
    output -> ccount = 0;
    output -> count = right - left;
    output -> data = &CharAt (left);
    send_chan (process);
    return 0;
}

/* Send a string to the process as input */
StringToProcess () {
    register char *proc_name = getstr ("To process: ");
    register char  *input_string;
    register struct process_blk *process;
    register struct wh *output;

    if ((process = find_process (proc_name)) == NULL) {
	error ("Process does not exist");
	return 0;
    }
    input_string = getstr ("String:");
    if (input_string == 0) return 0;
    output = &process -> p_chan.ch_outrec;
    if (output -> count || output -> ccount)
	error ("Overwriting data on blocked channel");
    output -> index = process -> p_chan.ch_index;
    output -> ccount = 0;
    output -> count = strlen (input_string);
    if (output -> count <= 0)
	error ("Null string");
    output -> data = input_string;
    send_chan (process);
    return 0;
}
   
/* Get the current output which has been thrown at us and send it
   to the user as a string; this is only allowed if MPX_chan is non-null
   indicating that this has been indirectly called from stuff_buffer
*/
ProcessOutput () {
    if (MPX_chan == NULL) {
	error ("process-output can only be called from filter");
	return 0;
    }

    MLvalue -> exp_type = IsString;
    MLvalue -> exp_release = 1;
    MLvalue -> exp_int = MPX_chan -> ch_count;
    MLvalue -> exp_v.v_string = (char *) malloc (MLvalue -> exp_int + 1);
    strcpyn (MLvalue -> exp_v.v_string, MPX_chan -> ch_ptr, MLvalue -> exp_int);
    MLvalue -> exp_v.v_string[MLvalue -> exp_int] = '\0';
    return 0;
}

/* Send an signal to the specified process group */
sig_process (signal) {
    register struct process_blk *process;
    register char  *buf = getstr ("Process in buffer: ");

    if ((process = find_process (buf)) == NULL) {
	error ("Not a process");
	return 0;
    }

/* We must update the process flag explicitly in the case of continuing a 
   process since no signal will come back */

    if (signal == SIGCONT) {
	sighold (SIGCHLD);
	process -> p_flag = (process -> p_flag & ~STOPPED) | RUNNING;
	sigrelse (SIGCHLD);
    }

    killpg (process -> p_id, signal);
    return 0;
}

IntProcess () {
    return (sig_process (SIGINT));
}

QuitProcess () {
    return (sig_process (SIGQUIT));
}

KillProcess () {
    return (sig_process (SIGKILL));
}

StopProcess () {
    return (sig_process (SIGSTOP));
}

ContProcess () {
    return (sig_process (SIGCONT));
}

/*
 *  guido said to replace EOTProcess with a dummy, but we think it works ok
 *  with only a slight fix.    purtilo
 *
 */

EOTProcess () {
    register struct process_blk *process;
    register char  *buf = getstr ("Process in buffer: ");
    struct {
	short   code,
	        arg;
    }       EOT_msg;
    register struct wh *output;

    if ((process = find_process (buf)) == NULL) {
	error ("Not a process");
	return (0);
    }
    output = &process -> p_chan.ch_outrec;
    if (output -> count || output -> ccount)
	error ("Overwriting on blocked channel");

    output -> index = process -> p_chan.ch_index;
    output -> count = 0;
    output -> ccount = sizeof (EOT_msg);
    output -> data = (char *) & EOT_msg;
    EOT_msg.code = EOF;    /* crude, no?         .... purtilo */
/*  EOT_msg.code = M_EOT;  <-- old line from the mpx world    */
    send_chan (process);
}
/* #endif */

/* Some useful functions on the process */
StrFunc (CurrentProcess,
    (current_process ? current_process -> p_chan.ch_buffer -> b_name : ""));

/* Return the name of the currently active process: it is defined as the name
   of the current buffer if is attached to an active process
*/
ActiveProcess () {
    register struct process_blk *p;

    for (p = process_list; p != NULL; p = p -> next_process)
	if (active_process (p) && (p -> p_chan.ch_buffer == bf_cur))
	    break;
    if (p == NULL)
	p = current_process;
    MLvalue -> exp_type = IsString;
    MLvalue -> exp_release = 0;
    if (p == NULL) {
	MLvalue -> exp_int = 0;
	MLvalue -> exp_v.v_string = NULL;
    }
    else {
	MLvalue -> exp_int = strlen (p -> p_chan.ch_buffer -> b_name);
	MLvalue -> exp_v.v_string = p -> p_chan.ch_buffer -> b_name;
    }
    return 0;
}

/* Change the current-process to the one indicated
*/
ChangeCurrentProcess () {
    register char  *p_name = getstr ("Process name: ");
    register struct process_blk *process;

    process = find_process (p_name);
    if (process == NULL) {
	error ("Not a process");
	return 0;
    }
    current_process = process;
    return 0;
}

/* Return the process' status
      -1 - not an active process
       0 - a stopped process
       1 - a running process
*/
ProcessStatus () {
    register char  *p_name = getstr ("Process name: ");
    register struct process_blk *process;

    MLvalue -> exp_type = IsInteger;
    process = find_process (p_name);
    if (process == NULL)
	MLvalue -> exp_int = -1;
    else
	if (process -> p_flag & RUNNING)
	    MLvalue -> exp_int = 1;
	else
	    MLvalue -> exp_int = 0;
    return 0;
}

/* Get the process id */
ProcessID () {
    register char  *p_name = getstr ("Process name: ");
    register struct process_blk *process;

    MLvalue -> exp_type = IsInteger;
    process = find_process (p_name);
    if (process == NULL)
	MLvalue -> exp_int = 0;
    else
	MLvalue -> exp_int = process -> p_id;
    return 0;
}
#endif

/* Initialize things on the multiplexed file.  This involves connecting the
   standard input to a channel on the  mpx file.  Someday we will be brave and
   close 0.
*/

InitMpx () {
#ifdef subprocesses
    register struct channel_blk *in;
    int     newfd;
    extern  child_sig ();
    struct sgttyb   new;
#ifdef uiuc
    mpxin -> ch_index = 0;
#else

#ifdef ce
    err_file = fopen("/usr/ce/.mpx.err", "a");
#endif
    err_id = getuid ();
 /* We will make children think they have a vanilla terminal */
    ioans_rec.code = M_IOANS;
    ioctl (0, TIOCGETP, &(ioans_rec.ioctl_ans));
    ioans.ccount = sizeof (ioans_rec);
    ioans.data = (char *) & ioans_rec;

 /* Answer TIOCGPGRP with a record containing a -1.  This is so
    csh will work. */
    iopg_rec.code = M_IOANS;
    *(int *)(&iopg_rec.ioctl_ans) = -1; /* gross! */
    iopg.ccount = sizeof iopg_rec;
    iopg.data = (char *) & iopg_rec;
 /* Finally, any other ioctls get reflected. */
    ioany_rec.code = M_IOANS;
    ioany.ccount = sizeof ioany_rec;
    ioany.data = (char *) & ioany_rec;


/* Open the multiplexed file with a file name so that it can be shared from
   the outside */

    if (sflag) {
	sprintfl(mpx_filename, sizeof mpx_filename, "%s/.emacs_share", getenv("HOME"));
	unlink(mpx_filename);	/* Remove the file first */
    }
    if ((mpx_fd = mpx (sflag ? mpx_filename : 0, 0666)) < 0) {
	fprintf (stderr, "Can't open the multiplexed file\n");
/*	RstDsp (); */	/* JCM - seems to cause infinite loop */
	exit (1);
    }
  
    if (sflag) chmod(mpx_filename, 0666);
    mpx_share = -1;		/* Illegal channel index until shared */

    mpxin -> ch_index = chan (mpx_fd);
    ioctl (mpx_fd, MXNBLK, 0);	/* set up non-blocking mode */
    if (mpxin -> ch_index == -1) {
	fprintf (stderr, "Couldn't get a channel to mpx file");
	RstDsp ();
	exit (1);
    }
    connect (0, extract (mpxin -> ch_index, mpx_fd), 0);
/*close(0);*/
#endif
    mpxin -> ch_ptr = NULL;
    mpxin -> ch_count = 0;
#endif
    sigset (SIGCHLD, child_sig);
#ifdef subprocesses

    DefStrVar ("MPX-process", "");
    MPX_process = NextInitVarDesc[-1];

/*     see if we can make that a little more ..... */
    ProcessBufferSize = 50000;	/* # of chars in buffer before truncating */
/*    Old size was
    ProcessBufferSize = 10000;	   # of chars in buffer before truncating 
 *    but we like it bigger ... doesn't seem to hurt.   purtilo
 */
    DefIntVar ("process-buffer-size", &ProcessBufferSize);

    defproc (StartProcess, "start-process");
    defproc (StartFilteredProcess, "start-filtered-process");
    defproc (InsertFilter, "insert-filter");
    defproc (ResetFilter, "reset-filter");
    defproc (ProcessFilterName, "process-filter-name");
    defproc (RegionToProcess, "region-to-process");
    defproc (StringToProcess, "string-to-process");
    defproc (IntProcess, "int-process");
    defproc (QuitProcess, "quit-process");
    defproc (KillProcess, "kill-process");
    defproc (StopProcess, "stop-process");
    defproc (ContProcess, "continue-process");
    defproc (EOTProcess, "eot-process");
    defproc (CurrentProcess, "current-process");
    defproc (ProcessStatus, "process-status");
    defproc (ChangeCurrentProcess, "change-current-process");
    defproc (ActiveProcess, "active-process");
    defproc (ProcessID, "process-id");
    defproc (ProcessOutput, "process-output");
    defproc (ListProcesses, "list-processes");
#endif
}


/************************** mchan.h *******************************/


#ifdef uiuc
struct wh {
	short index;
	short count;
	short ccount;
	char *data;
}; /* Replacing definition from #include <sys/mpx.h> */
#endif

#define debug(x) fprintf(stderr,"x:%d\n",x);fflush(stderr)
#define debugh(x) fprintf(stderr,"x");fflush(stderr)

#define mpx_getc(p) (--(p)->ch_count>=0? *(p)->ch_ptr++&0377:fill_chan(p))
#define mpxin (&stdin_chan)

#define STOPPED		(1<<0)
#define RUNNING		(1<<1)
#define EXITED		(1<<2)
#define SIGNALED	(1<<3)
#define CHANGED		(1<<6)

#define active_process(x) ((x!=NULL)&& (x->p_flag&(RUNNING|STOPPED)))

extern int errno;

/* mpx_msg is the structure of a mpx control message read from the mpx file
*/
struct mpx_msg
    {short mpx_code, mpx_arg;
     struct sgttyb mpx_ioctl;
    };

/* Structure records pertinent information about channels open on the mpx file
   There is one channel associated with each process.
*/
struct channel_blk
    {int ch_index;
     struct wh ch_outrec;	/* Output record */
     char *ch_ptr;		/* Pointer to next input character */
     short ch_count;		/* Count of characters remaining in buffer */
     struct buffer *ch_buffer;	/* Process is bound to this buffer */
     struct BoundName *ch_proc;	/* Procedure which gets called on output */
    };       

/* Standard input is connected to the multiplexed file using a channel 
   described by stdin_chan.
*/
extern struct channel_blk stdin_chan;

/* Structure for information needed for each sub process started */

struct process_blk
    {struct process_blk *next_process;	/* link to next process */
     char *p_name;			/* command that started process */
     short p_id;			/* process id (group id as well */
     char p_flag;			/* RUNNING, STOPPED, etc */
     char p_reason;			/* signal causing p_flag */
     struct channel_blk p_chan;		/* process i/o connected to channel */
    };

struct process_blk *process_list,	/* all existing processes */
                   *current_process;	/* the one that we're currently
					   dealing with */
int sflag;			/* the -s command line switch (enables the
				   share-emacs facility) */


/************************  dsp.c  ******************************/

/* Display routines */

/*		Copyright (c) 1981,1980 James Gosling		*/

#include "config.h"
#include "keyboard.h"
#include "buffer.h"
#include "window.h"
#include "display.h"
#include <sys/ioctl.h>
#include <sgtty.h>
#include <stdio.h>

struct sgttyb old;		/* The initial tty mode bits */

/* to save to old flow control characters while flow is managed by 
   the socket world.    purtilo   */

#ifdef uiuc
struct	tchars   oldtt;
struct	tchars *oldttp;
char	oldxoff, oldxon;
#endif

InitDsp () {
    struct sgttyb   sg;
    extern char _sobuf[];

#ifdef uiuc
    int newmode;
    oldttp = &oldtt;
#endif

    gtty (0, &old);
    sg = old;
    sg.sg_flags = (sg.sg_flags & ~(ECHO | CRMOD | XTABS)) | RAW;
    stty (0, &sg);

#ifdef uiuc
    ioctl( 0, TIOCGETC, oldttp );
    oldxon = oldttp->t_startc;
    oldttp->t_startc = 0377;
    oldxoff = oldttp->t_stopc;
    oldttp->t_stopc = 0377;
    ioctl( 0, TIOCSETC, oldttp );
    printf("%c",NULL);    /* remove this and get surprise ... purtilo */
#endif

    ScreenGarbaged = 1;
    setbuf (stdout, _sobuf);

    term_init ();
    if (tt.t_window) (*tt.t_window) (0);
}

RstDsp () {

#ifdef uiuc
    oldttp->t_startc = oldxon;
    oldttp->t_stopc = oldxoff;
    ioctl( 0, TIOCSETC, oldttp );
#endif

    if (tt.t_window) (*tt.t_window) (0);
    (*tt.t_topos) (ScreenLength, 1);
    (*tt.t_wipeline) (0);
    (*tt.t_cleanup) ();
    fflush (stdout);
    stty (0, &old);
}

joe@cvl.UUCP (Joseph I. Pallas) (08/19/83)

Purtilo@uiuccsb.UUCP claims that the select call should be changed from

the old line ...
  nfds = select (20, &readfds, &writefds, (int*)0, (int*)0);

the new line ...
  nfds = select (20, &readfds, &writefds, (int*)100000);

to keep emacs from doing a poll instead of blocking (I assume the
missing argument in the new line is an oversight).  The key point is
that this reveals a failure to understand the following critical line
in the manual section for select(2):

"If timeout is a zero pointer, the select blocks indefinitely.  To
affect [sic] a poll, the timeout argument should be non-zero, pointing
to a zero valued timeval structure."

A zero value passed to select does NOT indicate a poll, and a value of
100000 is meaningless--*((int *)100000) is definitely NOT 100000.

Joe Pallas
joe@cvl.uucp	(seismo!rlgvax!cvl!joe)
joe@cvl.arpa

chris@umcp-cs.UUCP (08/20/83)

Actually, the problem is that some people are running 4.1a and some
people are running 4.1c.  The specification for select changed!
The 4.1a select has 3 arguments; the 4.1c has 4.  Moreover, the
way timeouts are specified is different.  Anyway, to get around
all this, I'm going to post a whole slew of changes (as soon as I
finish reading all this news!) which I promised long ago.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci
UUCP:	{seismo,allegra,brl-bmd}!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris.umcp-cs@UDel-Relay

purtilo@uiuccsb.UUCP (08/23/83)

#R:uiuccsb:7400001:uiuccsb:7400003:000:1364
uiuccsb!purtilo    Aug 22 23:40:00 1983

Concerning that mchan.c fix:

Got word of a fix to the fix ...

  Date: 21 Aug 1983 13:46-EST
  From: James Gosling <ucbvax!cmuitca!jag@jag>
  Subject: Re: guido mchan fix (LONG MSG) - (nf)
  To: pur-ee!uiucdcs!uiuccsb!purtilo@BERKELEY
  In-Reply-To: pur-ee!uiucdcs!uiuccsb!purtilo's message of 18 Aug 83
  
  Unfortunatly, your "fix" is wrong and the original was right. I quote
  from the manual:
  
       If timeout is a non-zero pointer,  it  specifies  a  maximum
       interval  to wait for the selection to complete.  If timeout
       is a zero  pointer,  the  select  blocks  indefinitely.   To
       affect  a  poll,  the  timeout  argument should be non-zero,
       pointing to a zero valued timeval structure.
  
  So the correct value for the "timeout" parameter was the original zero
  pointer.  Also, you forgot the "exceptfds" parameter and to put the
  "timeout" in a pass-by-reference "timeval" structure.  Please post a
  correction to unix-emacs.
  
  				James.

-----------------

Well, my man page didn't read that way; a little poking around here 
uncovered for me a 4.1c man page which does read that way. We're
running 4.1a here, so if that's your flavor too then use the fix I
had posted, else use the fix to the fix (i.e. use the original text).

Looking forward to when the powers that be here get us 4.1c ...


							Jim Purtilo