Subject: Additional C-Kermit Implementation Notes for Minix
From: Adrian Godwin, 78 Putnoe Street, Bedford, England.
Date: 26 February 1988

Modifying C-Kermit 4D-061 for use under Andrew Tanenbaum's 'MINIX' has
required rather more changes to Minix than to Kermit. The C source files are
included; they all began as the CK---.--- files for the 4D(061) distribution
set. Here the names have been changed to MX---.--- . Hints, fixes and library
changes are also attached - most of these are applicable for anyone
implementing a serial i/o driver for Minix, and many library fixes are useful
for porting other utilities. 

	Kermit cannot be built under version 1.1 Minix, as it compiles to
about 85K and the initial Minix assembler cannot separate I&D model output.
The executable file was therefore built under MS-DOS using the Lattice 3.10 C 
compiler. Some care is needed in cross-compiling : see the notes in Tanenbaum's
book about libraries, and read the enclosed Lattice makefile, cktker.mak.

A port of this version to the latest C-Kermit version 4E(070) is now underway 
and will be released at some future time.

Associated Files:
=================

	I have included with this help file (cktker.hlp) the following :

mxcmai.c	-	Modified Kermit source files
mxutio.c	
mxufio.c	
mxuusr.c	
mxuus3.c	
mxcfns.c	

mxtker.mak	-	Makefile for Lattice 'lmk' and 3.10 compiler.

mxtker.inp	-	linker command file (included in mxtker.mak)

mxtker.boo	-	boo encoding of cktker.out

Changes to Kermit:
==================

ckutio.c	As usual, most of the changes are here. Fixes are largely
		a matter of picking a suitable option from the variety of
		code that exists. Use V7, but enable use of MYREAD and DON'T
		use the kmem (initrawq) functions. The uucp locks directory
		doesn't exist, but since it's a PC and you'll know if you're
		using the serial port, remove locking code altogether.
		For some reason, Lattice returns the unsigned char result
		of myread() expanded to a signed int. A badly read character
		can thus appear to other functions as an error code. Make
		myread return ((int)ch) & 0377.
		Ttychk and conchk need fixing to stop the accesses to kmem. 
		Reorder ttychk so that if myread() finds nothing in it's 
		buffer, it will execute other (system dependent) code, 
		like FIONREAD, to determine if typahead exists in the system 
		buffers. Then implement a FIONREAD ioctl call if you want. 
		It makes a slight improvement to 'connect' performance, but 
		is more worthwhile for conchk(), where nonblocking read is
		no use. The transfer escape characters can then be used.

ckcmai.c	Shorten the help message provided for remote help -
		Lattice can only compile strings less than 256 bytes long.
		Why isn't this list compiled from the command tables, like
		the command line interpreter's command completion?

ckuusr.c	Minix has a curious interpretation of printf, requiring
ckuus3.c	"%D" to indicate a long integer. Change all the "%ld" formats
		in these two files, or better still fix the do_printf() in the
		Minix library to accept K&R formats.
		Also calculate efficiency, etc. for statistics using long
		constants - Lattice makes a hash of it otherwise.

ckcfns.c	Calculate effective speed & efficiency for tlog using 
		specific long arithmetic. Use a long for x, the argument
		for F101 in TLOG.

ckufio.c	No include file <sys/files.h>. Lattice would prefer the 
		functions FILE *fopen(), *fdopen() to be declared as such.
		The function zclosif() attempts to kill a completed child
		process. If it has already completed, this fails, but MINIX
		still wants a wait() call to clean up the memory allocation,
		otherwise a fwe invocations of 'remote directory' will use
		up any remaining memory. This problem will also occur in the
		minix shell if you invoke all processes in the background.
		Use the shell command 'wait' to clean up completed processes
		(zombies).

Changes to Minix library routines.
==================================

	I started developing this set of library fixes when building Micro-
Emacs. A few of them may be unnecessary, but I recommend you fix them anyway.
Many routines are here because they aren't in the standard library.
The lattice makefile includes all these routines as an object called 'emlib' -
this is for debugging purposes only (they should be built into the library).
While not strictly a library change, fix dos2out before attempting to 
convert this large executable : the calculation of load_size will be wrong
unless all the operands are forced to type long. The calculation of the
total memory allocation, a_totb is also wrong, since it includes (for the 
separate I & D model) the text size a_text in the sum. This results in a 
154K memory allocation, which is not permitted. chmem =20000 will fix it.

	Some include file changes. Stdio.h defines a macro 'puts()' as an 
fputs() to stdout. This is wrong, as puts() should append a newline while 
fputs() doesn't. Remove the macro and create a library function.

There is no <sys/dir.h>. It should contain:

/* dir.h
 *
 * The structure of a directory entry.
 */

#define NAME_SIZE 14		/* defined in fs/const.h */

struct direct {
  inode_nr d_ino;		/* inode number */
  char d_name[NAME_SIZE];	/* name fills it out to 16 bytes */
} ;

	The file h/type.h is needed by kermit, but included as <sys/types.h>. 
Create a <sys/types.h> that includes h/type.h.

	Sgtty.h also needs some changes to define tty functions that don't 
exist in standard Minix. The kermit I have built used this version of sgtty.h, 
so if you want to use that executable, your tty driver must correspond. The 
i/o functions are an upwards compatible superset of the Minix sgtty functions,
and are inspired by SCO Xenix. Note particularly the implementation of
stty() and gtty() as macros. 

/* sgtty.h 
 *
 * Data structures for IOCTL. 
 */

struct sgttyb {
  char sg_ispeed;		/* input speed      (has precedence)	*/
  char sg_ospeed;		/* output speed     (may be ignored)	*/
  char sg_erase;		/* erase character 			*/
  char sg_kill;			/* kill character 			*/
  int  sg_flags;		/* mode flags 				*/
};

/* simulate stty and gtty with ioctl					 */

#define stty(fd, arg)	ioctl(fd, TIOCSETP, arg)
#define gtty(fd, arg)	ioctl(fd, TIOCGETP, arg)

struct tchars {
  char t_intrc;			/* SIGINT char 				*/
  char t_quitc;			/* SIGQUIT char 			*/
  char t_startc;		/* start output (initially CTRL-Q) 	*/
  char t_stopc;			/* stop output	(initially CTRL-S) 	*/
  char t_eofc;			/* EOF (initially CTRL-D) 		*/
  char t_brkc;			/* input delimiter (like nl) 		*/
};

/* Fields in sg_flags. */

#define COOKED       0000000	/* neither CBREAK nor RAW 		*/
#define	TANDEM	     0000001	/* XON XOFF flowcontrol on input	*/
#define CBREAK	     0000002	/* enable cbreak mode 			*/
#define LCASE        0000004	/* support lower case			*/
#define ECHO	     0000010	/* echo input 				*/
#define CRMOD	     0000020	/* map lf to cr + lf 			*/
#define RAW	     0000040	/* enable raw mode 			*/
#define ODDP         0000100	/* odd parity				*/
#define EVENP        0000200	/* even parity				*/
#define ANYP         0000300	/* parity not set / ignored		*/
#define XTABS	     0006000	/* do tab expansion 			*/

#define TIOCGETP (('t'<<8) | 8)
#define TIOCSETP (('t'<<8) | 9)
#define TIOCGETC (('t'<<8) | 18)
#define TIOCSETC (('t'<<8) | 17)

/*
 * I can't find a 'standard' definition for this one, so I've assigned
 * a '1' arbitrarily.
 */

#define FIONREAD (('f'<<8) | 1)

/* Baud rate settings for async ttys. */

#define B0		0		/* hangup line (drop DTR)	*/
#define B50		1
#define B75		2
#define B110		3
#define B134		4
#define B150		5
#define B200		6
#define B300		7
#define B600		8
#define B1200		9
#define B1800		10
#define B2400		11
#define B4800		12
#define B9600		13

#define EXTA		14
#define B19200		EXTA

#define EXTB		15
#define B38400		EXTB

	There are no time management calls in MINIX other than time, times()
etc. I have defined asctime() and gmtime() functions based on the conversions
done in date.c, but avoided support for timezones and daylight saving.

/* time.h : structure and function definitions for time library calls. */

struct tm {
	int	tm_sec;
	int	tm_min;
	int	tm_hour;
	int	tm_mday;
	int	tm_mon;
	int	tm_year;
	int	tm_wday;
	int	tm_yday;
	int	tm_isdst;	/* not currently supported */
};

extern struct tm	*localtime();
extern struct tm 	*gmtime();
extern char		*asctime();

/* system(command)
 * 
 * Code copied from the MINIX make utility 'mysystem' function, with
 * minor modifications.
 */

char *_defpath = "/bin/sh";

system(cmd)
char *cmd;
{
    int ccode,pid,status;
    char *shell, *getenv();

    if ( (shell = getenv("SHELL")) == NULL)
	shell = _defpath;

    if ( (pid = fork()) == 0 ) {	/* child execs a shell */
	execl(shell,shell,(*cmd ? "-c" : "-i"),cmd,0);
    }

    if ( pid < 0 ) {  			/* parent waits for child */
	return(pid);
    }
    else {
	while ( ((ccode = wait(&status)) != pid) && (ccode != -1))
		;
	return(status);
    }
}

/* This function copied from the MINIX library, but with some bugs fixed. */

char *getenv(name)
register char *name;
{
  extern char **environ;
  register char **v = environ, *p, *q;

  while ((p = *v++) != NULL) {		/* fix : increment v */
	q = name;
	while (*p++ == *q)
		if (*q++ == 0)
			break;	/* fix : break rather than continue */
	if (*(p - 1) != '=')
		continue;
	return(p);
  }
  return(0);
}

/* getc
 *
 * fixed to ensure _count always indicates number of chars in the buffer.
 * - used to indicate correctly only when zero. Decrement _count after
 * taking char from recently filled buffer. Note : fseek() had a bodge 
 * which allowed it to work with this error. This also requires a fix.
 *
 * note when rebuilding library : this module should appear AFTER scanf,
 * which calls it, to ensure it can be found.
 */

#include "stdio.h"

getc(iop)
FILE *iop;
{
	int ch;

	if ( testflag(iop, (_EOF | _ERR )))
		return (EOF); 

	if ( !testflag(iop, READMODE) ) 
		return (EOF);

	if (--iop->_count < 0){		/* changed from <= 0 */

		if ( testflag(iop, UNBUFF) )
			iop->_count = read(iop->_fd,&ch,1);
		else 
			iop->_count = read(iop->_fd,iop->_buf,BUFSIZ);

		if (iop->_count <= 0){
			if (iop->_count == 0)
				iop->_flags |= _EOF;
			else 
				iop->_flags |= _ERR;

			return (EOF);
		}
		else {
			iop->_ptr = iop->_buf;
			iop->_count--;		/* inserted */
		}
	}

	if (testflag(iop,UNBUFF))
		return (ch & CMASK);
	else
		return (*iop->_ptr++ & CMASK);
}

/* ttyname() - missing library function. Gets the inode number of the 
 * passed filedescriptor, then searches the /dev directory for a match.
 * Returns a pointer to the name (static data) or NULL.
 */

#include "stdio.h"
#include "stat.h"
#include "dir.h"

char *ttyname(fd)
int fd;
{
  struct stat s;
  int n, dd;
  char *p = NULL;
  static struct direct d[2];	/* big enough to nul-terminate name */

  if (fstat(fd, &s) != 0 || (s.st_mode&S_IFMT) != S_IFCHR)
	return(NULL);

  /* isatty. now search /dev for the inode. */

	n = s.st_ino;
	if ((dd = open("/dev", 0)) < 0)
		return NULL;		/* cannot read directory */

	while (read( dd, d, sizeof(*d)) == sizeof(*d)) 

		if (d[0].d_ino == n) {
			p = d[0].d_name;
			p[sizeof(*d)] = '\0';
			break;
		}
	close(dd);
	return(p);
}

/* fdopen() - missing library function. Returns a file pointer set up from
 * the passed file descriptor, or NULL if it can't.
 */

#include "stdio.h"

FILE *fdopen(fd,mode)
int fd;
char *mode;
{
	register int i;
	FILE *fp;
	char *malloc();
	int flags = 0;

	for (i = 0; _io_table[i] != 0 ; i++) 
		if ( i >= NFILES )
			return(NULL);

	switch(*mode){

	case 'w':
	case 'a': 
		flags |= WRITEMODE;
		break;

	case 'r':
		flags |= READMODE;	
		break;

	default:
		return(NULL);
	}

	if (( fp = (FILE *) malloc (sizeof( FILE))) == NULL )
		return(NULL);

	fp->_count = 0;
	fp->_fd = fd;
	fp->_flags = flags;
	fp->_buf = malloc( BUFSIZ );
	if ( fp->_buf == NULL )
		fp->_flags |=  UNBUFF;
	else 
		fp->_flags |= IOMYBUF;

	fp->_ptr = fp->_buf;
	_io_table[i] = fp;
	return(fp);
}

/* modified exit() which flushes stdio buffers before dying. This means
 * standard io library is included whether you wanted it or not - add a
 * dummy "_cleanup() {}" declaration to avoid it if you're writing very
 * small programs like the Minix utilities. This is more likely to
 * be compatible with other UNIX programs than the exit() in the standard
 * release.
 */ 

#include "../include/lib.h"

int exit(status)
int status;
{
  _cleanup();
  return callm1(MM, EXIT, status, 0, 0, NIL_PTR, NIL_PTR, NIL_PTR);
}

/* strncmp() - library module, changed to operate correctly when
 * n is zero. (causes problem in kermit interactive command processor)
 */

int strncmp(s1, s2, n)
register char *s1, *s2;
int n;
{
/* Compare two strings, but at most n characters. */

  while (n-- != 0) {
	if (*s1 != *s2) return(*s1 - *s2);
	if (*s1 == 0) return(0);
	s1++;
	s2++;
  }
  return 0;
}

/* These functions based on the similar operations in date.c. No support
 * for timezones or daylight saving - localtime() currently just calls 
 * gmtime() and returns the results unmodified.
 */

#include <time.h>

static int _days_per_month[] =
  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static char *_months[] =
  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

static char *_days[] =
  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

static long _s_p_min  = 60L;
static long _s_p_hour = 60L * 60L;
static long _s_p_day  = 60L * 60L * 24L;
static long _s_p_year = 60L * 60L * 24L * 365L;

char *asctime(tmp)
struct tm *tmp;
{
  static char _asctbuf[30];

  sprintf(_asctbuf,"%s %s %2d %02d:%02d:%02d %d\n", 
		_days[tmp->tm_wday], _months[tmp->tm_mon], 
		tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, 
		tmp->tm_year + 1900); 

  return _asctbuf;
}

struct tm *gmtime(ltp)
long *ltp;
{
  static struct tm _tm;
  char  *p;
  long   t = *ltp;

  for (p = (char *)&_tm; p < ((char *)&_tm) + sizeof(_tm); p++)
	*p = 0;

  /* get day-of-week. (Add 4 because 1.1.70 was a Thursday) */
  _tm.tm_wday = ((t / _s_p_day)+4L) % 7L;

  /* reduce t by years and days, leaving seconds this year. */
  while (t >= _s_p_year) {
	if (((_tm.tm_year + 2) % 4) == 0)
		t -= _s_p_day;
	_tm.tm_year += 1;
	t -= _s_p_year;
  }
  _tm.tm_yday = t / _s_p_day;

  /* adjust february for leap year and reduce to seconds this month */
  if (((_tm.tm_year + 2) % 4) == 0)
	_days_per_month[1]++;
  while ( t >= (_days_per_month[_tm.tm_mon] * _s_p_day))
	t -= _days_per_month[_tm.tm_mon++] * _s_p_day;
  _days_per_month[1] = 28;
  _tm.tm_year += 70;

  /* find day of month */
  _tm.tm_mday = 1;
  while (t >= _s_p_day) {
	t -= _s_p_day;
	_tm.tm_mday++;
  }

  /* and time today */
  while (t >= _s_p_hour) {
	t -= _s_p_hour;
	_tm.tm_hour++;
  }
  while (t >= _s_p_min) {
	t -= _s_p_min;
	_tm.tm_min++;
  }
  _tm.tm_sec = (int) t;

  return &_tm;
}

struct tm *localtime(ltp)
long *ltp;
{
  return gmtime(ltp);
}

The following fixes may be regarded as dubious, since they actually make
the operation of minix less like standard unix. However, the performance
of unbuffered tty output (as used in kermit help messages and command 
completion) is so appalling I felt something was needed. 

If a call is made to any of the string output routines (printf, puts ..),
when the channel is unbuffered, then a temporary buffer is attached to
the channel for the duration of the call and flushed at the end. Since
setting stdout unbuffered freed it's buffer allocation, the buffer used
for this is probably already available on the heap. 

The speed-up effect of this on unbuffered i/o (as used for all stderr
in the system utilities) is startling, and I can't think why these changes 
should cause any incompatibilities. 

#include <stdio.h>

/* tmpbuf() creates a temporary buffer for the file, if possible.
 * Only call this if the file is unbuffered.
 */

static char *_locbuf = NULL;
static int _locflags;

static void tmpbuf(file)
register FILE *file;
{
    if (_locbuf == NULL)		/* no buffer - get one */
	_locbuf = malloc(BUFSIZ);

    if (_locbuf == NULL)		/* no space - cannot buffer */
	return;

    _locflags = file->_flags;
    file->_flags &= ~UNBUFF;
    file->_buf = file->_ptr = _locbuf;
    file->_count = 0;
}

/* endbuf() restores the FILE flags and flushes the buffer if required. */

static endbuf(file, fflag)
register FILE *file;
int fflag;
{
    if (fflag || file->_buf == _locbuf)
	fflush(file);

    if (file->_buf == _locbuf && _locbuf != NULL) {

	/* temporary buffer used - restore old settings */
	file->_buf = file->_ptr = NULL;
	file->_count = 0;
	file->_flags = _locflags;
    }
}

/* puts() - replaces macro in stdio.h, since a newline is required 
 * after the given string. 
 */

puts(s)
register char *s;
{
	if ( testflag(stdout, UNBUFF) )
		tmpbuf(stdout);

	while ( *s ) 
		putc(*s++,stdout);
	putc('\n',stdout);

	endbuf(stdout, 1);	/* always flush */
}

fputs(s,file)
register char *s;
FILE *file;
{
	if ( testflag(file, UNBUFF) )
		tmpbuf(file);

	while ( *s ) 
		putc(*s++,file);

	endbuf(file, 0);
}

fprintf (file, fmt, args)
FILE *file;
char *fmt;
int args;
{
	if ( testflag(file, UNBUFF) )
		tmpbuf(file);

	_doprintf (file, fmt, &args);

	endbuf(file, testflag(file, PERPRINTF));
}

printf (fmt, args)
char *fmt;
int args;
{
	if ( testflag(stdout, UNBUFF) )
		tmpbuf(stdout);

	_doprintf (stdout, fmt, &args);

	endbuf(stdout, testflag(stdout, PERPRINTF));
}

Changes to Minix
================

	Here's where it gets more experimental: I've done the following fixes 
to MINIX in order to get Kermit working or to solve problems I've found along 
the way. Some may not be essential - I didn't want to rebuild with all my 
older fixes taken out just to determine which are necessary. Since all the 
fixes are for major problems, they're worth doing anyway.

	Any SIGALRM sent to a process will cancel all pending alarms. This 
has the effect of suspending forever (say) the update process if another
process executes sleep(). MM/signal.c, check_sig() resets not only the 
alarm bit for process getting the signal, but for all others. Resetting the
bit needs to be conditional on the state of the send_sig flag. The clock
task looks in the message field CLOCK_PROC_NR for the process number of
the task that needs the alarm signal. MM actually puts the number in the 
field PROC_NR, which is not the same. Hence the clock actually sends the 
signal to process 0, which is taken to mean all processes.

	It is possible for a message from FS to a task to be overwritten 
before it reaches its destination if the task supports multiple minor
devices and process suspension, as a TTY driver with more than one line 
would. This will occur if the TTY driver has suspended a task, and is busy
with an interrupt request (which will revive the task) when FS send-receives
a message about a different minor device. FS will be forced to wait for the 
task until the REVIVE message appears. FS is then sent the message, since it 
appears to be waiting for a reply. The tty task will then look for more 
messages and will find the FS request, now overwritten with the REVIVE message. 
The TTY will be given its own REVIVE message and reject it as invalid. FS 
will continue to wait for a reply to it's TTY request (now lost) and hang.

	I have fixed this by treating REVIVE messages in the same way as 
signals they are rather similar, since they are sent to a caller which is not
waiting for them. Instead of replying with a REVIVE message, I call
set_revive(), a function similar to cause_sig() which sets a flag in the 
proc table p_flags and saves the reply status in a new member, p_status.
Inform() is then called for FS as well as MM, and searches for the revive
flag. Any pending revives are actioned as a HARDWARE message, just like a 
kernel signal. FS will then no longer get a reply from a previous message
when send-receiving to a task, since the REVIVE call comes from HARDWARE.
FS can thus be simplified in areas like rw_dev().

	FS may panic with a "couldn't revive anyone" error when it recovers 
a process from waiting on a broken pipe. If the pipe is reading from two file
descriptors (as in the Kermit procedure to pipe process stdout and stderr to 
a remote machine for the REMOTE HOST command), then FS/misc.c, do_exit() will 
call FS/open.c, do_close() for both descriptors. On each call, FS/pipe.c, 
release() will be called, setting the REVIVING flag for the reading process (e.g. the
Kermit) twice, and also incrementing 'reviving'. FS/main.c, get_work() revives
the reading process, then can't find a process to account for the other 
increment of 'reviving'. release() should check for the REVIVING flag before
calling revive() again.

	FS can send a cancel message for an out-of-range tty number when a 
process is killed, due to FS/pipe.c, do_unpause() not setting up the global 
fp before calling get_filp(). The tty driver does not catch out-of-range line 
numbers on either messages or characters from the overrun buffer, and can 
index a tty structure that doesn't exist.

	Do_unpause() doesn't check that get_filp() returns a valid result.
The return can be NULL if the file (that the process is paused on) has been
closed. This happens if a signal killed the process. The distributed code of 
MM/signal.c, check_sig() differs from the printed listings in Tanenbaum's 
book in the order of execution of unpause() and sig_proc(). This means that 
FS expects do_unpause to have a process to restart, when the filehandles it 
should look at are already closed. It seems OK to return the order of the 
calls to their original state, but I'd like to know why they changed.

	CONTROL-S will pause the user process by waiting before replying to 
FS. This causes the entire system to get stuck waiting for FS to return from
the TTY call. The TTY needs to SUSPEND the process on output, just as it does 
on input. Do_cancel() in the TTY driver also has to change to ensure that FS
obtains a reply or a REVIVE when output is completed.

The following comments are not bug reports, but code changes I have done
to improve the system's performance at high interrupt rates. I found
character processing at 9600 baud on an 8MHz 8086 a problem, and had to
make several changes :

	When interrupts are arriving at high speed (characters at 9600 baud),
some may be lost due to the time taken to process an interrupt in the kernel.
This time may be reduced by moving the check for now-ready tasks in kernel/
proc.c, interrupt() to the end of mini-rec(). This reduces the number of calls
to mini-send within the interrupt handler to 1. If a clock interrupt was 
pending, the clock processing can double or triple the time needed to process
the call to interrupt(). If the tty task is often scheduled, the clock task
is often behind, since tasks do not preempt each other. Thus clock interrupts
often are pending when interrupt() is called at high repetition rates.

	However, placing this check in mini_rec() will mean that interrupts 
will always be serviced when the task becomes ready. This, while reducing the 
interrupt latency of a task, means that fast interrupts can make the task 
permanently computable. No other process can then obtain any time to read 
the incoming characters, and the task's buffer fills. Flowcontrol and possibly
ignoring unbufferable input makes it possible to empty the buffer again. 
File transfer with Kermit is OK, because the packets are considerably smaller
than the task's buffer, but long packets, sliding windows and connect sessions
will have problems if flowcontrol is not possible. 

	The mini_send() call in interrupt() should also be removed and the 
message copy be performed directly within interrupt(), avoiding the 
substantial checking performed on call parameters, which should be unnecessary 
on messages within the kernel. The message copy might also be performed by 
a faster version of copy_mess(), for use within the kernel, which need not
span segments. Better still, since interrupts may overwrite each other and 
only affect a task's private data area, they tend to use a single known
buffer for data transfer to the calling task. (I haven't made these last 2 changes yet).

The following code fragments indicate most of the above fixes. Since the code
was originally that distributed by Prentice-Hall, I have included their
copyright notice.

/* Copyright (C) 1987 by Prentice-Hall, Inc.  Permission is hereby granted to
 * private individuals and educational institutions to modify and
 * redistribute the binary and source programs of this system to other
 * private individuals and educational institutions for educational and
 * research purposes.  For corporate or commercial use, permission from
 * Prentice-Hall is required.  In general, such permission will be granted,
 * subject to a few conditions.
 */

=============
Kernel/proc.c
=============

/*===========================================================================*
 *				interrupt				     * 
 *===========================================================================*/
PUBLIC interrupt(task, m_ptr)
int task;			/* number of task to be started */
message *m_ptr;			/* interrupt message to send to the task */
{
/* An interrupt has occurred.  Schedule the task that handles it. */

  int this_bit;
  register struct proc *dest_ptr;

#ifdef ibmpc
  /* Re-enable the 8259A interrupt controller. */
  this_bit = 1 << (-task);
  port_out(INT_CTL, ENABLE);	/* this re-enables the 8259A controller chip */
  if (pc_at) port_out(INT2_CTL, ENABLE);	/* re-enable second 8259A */
#endif

  /* Try to send the interrupt message to the indicated task. */
  this_bit = 1 << (-task);
  dest_ptr = proc_addr(task);
  if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) 
  &&   (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == HARDWARE) ) {

	/* Destination is indeed waiting for this message. */
	cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys, m_ptr, 
			dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
	dest_ptr->p_flags &= ~RECEIVING;	/* deblock destination */
	if (dest_ptr->p_flags == 0) ready(dest_ptr);
	busy_map &= ~this_bit;	/* turn off the bit in case it was on */

  } else {

	/* The message could not be sent to the task; it was not waiting. */
	if (task == CLOCK) {
		lost_ticks++;
	} else {
		busy_map |= this_bit;		/* mark task as busy */
		task_mess[-task] = m_ptr;	/* record message pointer */
	}
  }

  /* If a task has just been readied and a user is running, run the task. */
  if (rdy_head[TASK_Q] != NIL_PROC && (cur_proc >= 0 || cur_proc == IDLE))
	pick_proc();
}

/*===========================================================================*
 *				mini_send				     * 
 *===========================================================================*/
PUBLIC int mini_send(caller, dest, m_ptr)
int caller;			/* who is trying to send a message? */
int dest;			/* to whom is message being sent? */
message *m_ptr;			/* pointer to message buffer */
{
/* Send a message from 'caller' to 'dest'.  If 'dest' is blocked waiting for
 * this message, copy the message to it and unblock 'dest'.  If 'dest' is not
 * waiting at all, or is waiting for another source, queue 'caller'.
 */

  register struct proc *caller_ptr, *dest_ptr, *next_ptr;
  vir_bytes vb;			/* message buffer pointer as vir_bytes */
  vir_clicks vlo, vhi;		/* virtual clicks containing message to send */
  vir_clicks len;		/* length of data segment in clicks */

  /* User processes are only allowed to send to FS and MM.  Check for this. */
  if (caller >= LOW_USER && (dest != FS_PROC_NR && dest != MM_PROC_NR))
#ifdef DEBUG
	if (dest != SYSTASK) return(E_BAD_DEST);
#else
	return(E_BAD_DEST);
#endif
  caller_ptr = proc_addr(caller);	/* pointer to source's proc entry */
  dest_ptr = proc_addr(dest);	/* pointer to destination's proc entry */
  if (dest_ptr->p_flags & P_SLOT_FREE) return(E_BAD_DEST);	/* dead dest */

  /* Check for messages wrapping around top of memory or outside data seg. */
  len = caller_ptr->p_map[D].mem_len;
  vb = (vir_bytes) m_ptr;
  vlo = vb >> CLICK_SHIFT;	/* vir click for bottom of message */
  vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT;	/* vir click for top of message */
  if (vhi < vlo || vhi - caller_ptr->p_map[D].mem_vir >= len)return(E_BAD_ADDR);

  /* Check to see if 'dest' is blocked waiting for this message. */

  if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) &&
		(dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == caller) ) {

	/* Destination is indeed waiting for this message. */
	cp_mess(caller, caller_ptr->p_map[D].mem_phys, m_ptr, 
				dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
	dest_ptr->p_flags &= ~RECEIVING;	/* deblock destination */
	if (dest_ptr->p_flags == 0) ready(dest_ptr);

  } else {
	/* Destination is not waiting.  Block and queue caller. */
	if (caller == HARDWARE) return(E_OVERRUN);
	caller_ptr->p_messbuf = m_ptr;
	caller_ptr->p_flags |= SENDING;
	unready(caller_ptr);

	/* Process is now blocked.  Put in on the destination's queue. */
	if ( (next_ptr = dest_ptr->p_callerq) == NIL_PROC) {
		dest_ptr->p_callerq = caller_ptr;
	} else {
		while (next_ptr->p_sendlink != NIL_PROC)
			next_ptr = next_ptr->p_sendlink;
		next_ptr->p_sendlink = caller_ptr;
	}
	caller_ptr->p_sendlink = NIL_PROC;
  }

#ifdef DEBUG
/* This message has been successfully handled. See if it should be saved
 * in the message log. 
 */
  if ((save_flags[caller+NR_TASKS] & CALL_LOG) 
  ||  (save_flags[dest  +NR_TASKS] & DEST_LOG))
	save_msg(caller, dest, m_ptr);
#endif

  return(OK);
}

/*===========================================================================*
 *				mini_rec				     * 
 *===========================================================================*/
PRIVATE int mini_rec(caller, src, m_ptr)
int caller;			/* process trying to get message */
int src;			/* which message source is wanted (or ANY) */
message *m_ptr;			/* pointer to message buffer */
{
/* A process or task wants to get a message.  If one is already queued,
 * acquire it and deblock the sender.  If no message from the desired source
 * is available, block the caller.  No need to check parameters for validity.
 * Users calls are always sendrec(), and mini_send() has checked already.  
 * Calls from the tasks, MM, and FS are trusted.
 */

  register struct proc *caller_ptr, *sender_ptr, *prev_ptr;
  int sender, this_bit;
  int locked = FALSE;

  caller_ptr = proc_addr(caller);    /* pointer to caller's proc structure */

  /* if we're ready to receive, find a sender. Else just block. */
  if ((caller_ptr->p_flags & SENDING) == 0) {

    /* Check to see if a message from desired source is already available. */

    sender_ptr = caller_ptr->p_callerq;
    while (sender_ptr != NIL_PROC) {
	sender = sender_ptr - proc - NR_TASKS;
	if (src == ANY || src == sender) {
		/* An acceptable message has been found. */
		cp_mess(sender, sender_ptr->p_map[D].mem_phys, sender_ptr->p_messbuf,
					caller_ptr->p_map[D].mem_phys, m_ptr);
		sender_ptr->p_flags &= ~SENDING;	/* deblock sender */
		if (sender_ptr->p_flags == 0) ready(sender_ptr);
		if (sender_ptr == caller_ptr->p_callerq)
			caller_ptr->p_callerq = sender_ptr->p_sendlink;
		else
			prev_ptr->p_sendlink = sender_ptr->p_sendlink;
		return(OK);
	}
	prev_ptr = sender_ptr;
	sender_ptr = sender_ptr->p_sendlink;
    }

    /* If the caller is a task, there may be an interrupt waiting. 
     * Check now, rather than before checking callers, so fast interrupts
     * give the process a chance to absorb them. Lock between determining the
     * state of busy_map and setting RECEIVING so an interrupt occurring 
     * between cannot be forgotten.
     */

    if (caller < HARDWARE && (src == ANY || src == HARDWARE)) {

	lock();
	locked = TRUE;
	this_bit = 1 << (-caller);
	if (busy_map & this_bit) {
		cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys,
			task_mess[-caller], caller_ptr->p_map[D].mem_phys,
			m_ptr);
		busy_map &= ~this_bit;		/* must be locked here too */
		restore();
		return(OK);
	}
    }
  }

  /* No suitable message is available. Block the process trying to receive. */

  caller_ptr->p_getfrom = src;
  caller_ptr->p_messbuf = m_ptr;
  caller_ptr->p_flags |= RECEIVING;
  if (locked) restore();
  unready(caller_ptr);

  /* If MM has just blocked and there are kernel signals pending, now is the
   * time to tell MM about them, since it will be able to accept the message.
   * Also applies to revive messages for FS. No problems with receive-before-
   * send, since (src == ANY) cannot be true for sendrec messages.
   */

  if (rev_procs > 0 && caller == FS_PROC_NR && src == ANY) inform(FS_PROC_NR);
  if (sig_procs > 0 && caller == MM_PROC_NR && src == ANY) inform(MM_PROC_NR);
  return(OK);
}

===============
kernel\system.c
===============

/*===========================================================================*
 *				set_revive				     * 
 *===========================================================================*/
PUBLIC set_revive(proc_nr, status)
int proc_nr;
int status;
/* Similar function to cause_sig. Tasks sending a revive message directly 
 * to FS may collide with a send in the opposite direction. Therefore, this
 * function saves the event and associated status (error code, bytes read etc)
 * in the target process' proc entry, and the kernel informs FS later with
 * inform(FS_PROC_NR). It would be nice to share the proc table's p_pending
 * member with MM, but the signals recorded in it may be ignored - only MM
 * knows. Note that the device's id is lost, and only FS may receive a message.
 * This is not currently a problem.
 */
{
  register struct proc *rp;

  rp = proc_addr(proc_nr);
  if (rp->p_flags & NEED_REVIVE) 
	panic ("cannot revive twice",proc_nr);
  rp->p_flags |= NEED_REVIVE;
  rp->p_rstatus = status;
  rev_procs++;

  inform(FS_PROC_NR);		/* perhaps it's safe to do it now ? */
}

/*===========================================================================*
 *				inform					     * 
 *===========================================================================*/
PUBLIC inform(proc_nr)
int proc_nr;			/* MM_PROC_NR or FS_PROC_NR */
{
/* When a signal is detected by the kernel (e.g., DEL), or generated by a task
 * (e.g. clock task for SIGALRM), cause_sig() is called to set a bit in the
 * p_pending field of the process to signal.  Then inform() is called to see
 * if MM is idle and can be told about it.  Whenever MM blocks, a check is
 * made to see if 'sig_procs' is nonzero; if so, inform() is called.
 * Likewise REVIVE messages for FS. These are sent only when FS is ready to
 * receive. Code here changed to check recipient is not blocked on send as
 * well as receive.
 */

  register struct proc *rp, *mmp;
  int r;

  /* If MM/FS is not waiting for new input, forget it. */
  mmp = proc_addr(proc_nr);
  if ( ((mmp->p_flags & RECEIVING) == 0) || mmp->p_getfrom != ANY) return;

  /* If MM is waiting for new input,  find a process with pending signals. */
  if (proc_nr == MM_PROC_NR) {
    for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
	if (rp->p_pending != 0) {
		m.m_type = KSIG;
		m.PROC1 = rp - proc - NR_TASKS;
		m.SIG_MAP = rp->p_pending;
		sig_procs--;
		if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK) 
			panic("can't inform MM : ", r);
		rp->p_pending = 0;	/* the ball is now in MM's court */
		return;
	}
  }

  /* If FS is waiting for new input,  find a process with pending revive. */
  else if (proc_nr == FS_PROC_NR) {
    for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
	if (rp->p_flags & NEED_REVIVE) {
		m.m_type = REVIVE;
		m.REP_PROC_NR = rp - proc - NR_TASKS;
		m.REP_STATUS  = rp->p_rstatus;
		rev_procs--;
		rp->p_flags &= ~NEED_REVIVE;
		if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK) 
			panic("can't inform FS : ", r);
		return;		/* the ball is now in FS's court */
	}
  }
}

===========
mm/signal.c
===========

/*===========================================================================*
 *				check_sig				     *
 *===========================================================================*/
PRIVATE int check_sig(proc_id, sig_nr, send_uid)
int proc_id;			/* pid of process to signal, or 0 or -1 */
int sig_nr;			/* which signal to send (1-16) */
uid send_uid;			/* identity of process sending the signal */
{
/* Check to see if it is possible to send a signal.  The signal may have to be
 * sent to a group of processes.  This routine is invoked by the KILL system
 * call, and also when the kernel catches a DEL or other signal. SIGALRM too.
 */

  register struct mproc *rmp;
  int count, send_sig;
  unshort mask;
  extern unshort core_bits;

  if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL);
  count = 0;			/* count # of signals sent */
  mask = 1 << (sig_nr - 1);

  /* Search the proc table for processes to signal.  Several tests are made:
   * 	- if proc's uid != sender's, and sender is not superuser, don't signal
   *	- if specific process requested (i.e., 'procpid' > 0, check for match
   *	- if a process has already exited, it can't receive signals
   *	- if 'proc_id' is 0 signal everyone in same process group except caller
   */
  for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) {
	if ( (rmp->mp_flags & IN_USE) == 0) continue;
	send_sig = TRUE;	/* if it's FALSE at end of loop, don't signal */
	if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE;
	if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE;
	if (rmp->mp_flags & HANGING) send_sig = FALSE;   /*don't wake the dead*/
	if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE;
	if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE;

	/* SIGALARM is a little special.  When a process exits, a clock signal
	 * can arrive just as the timer is being turned off.  Also, turn off
	 * ALARM_ON bit when timer goes off to keep it accurate.
	 */
	/* Change : only reset the ALARM_ON bit for processes that are
	 * going to get the signal - don't wipe out all the other alarms! 
	 */

	if (send_sig && sig_nr == SIGALRM) {
		if ( (rmp->mp_flags & ALARM_ON) == 0) continue;
		rmp->mp_flags &= ~ALARM_ON;
	}

	if (send_sig == FALSE || rmp->mp_ignore & mask) continue;

	/* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */
	unpause(rmp - mproc);	/* check to see if process is paused */
	count++;

	/* Send the signal or kill the process, possibly with core dump. */
	sig_proc(rmp, sig_nr);

	if (proc_id > 0) break;	/* only one process being signalled */
  }

  /* If the calling process has killed itself, don't reply. */
  if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE;
  return(count > 0 ? OK : ESRCH);
}

/*===========================================================================*
 *				set_alarm				     *
 *===========================================================================*/
PUBLIC int set_alarm(proc_nr, sec)
int proc_nr;			/* process that wants the alarm */
unsigned sec;			/* how many seconds delay before the signal */
{
/* This routine is used by do_alarm() to set the alarm timer.  It is also
 * to turn the timer off when a process exits with the timer still on.
 */

  int remaining;

  m_sig.m_type = SET_ALARM;

/*---  m_sig.PROC_NR = proc_nr;	---*/
  m_sig.CLOCK_PROC_NR = proc_nr;	/* clock uses different member 	*/

  m_sig.DELTA_TICKS = HZ * sec;
  if (sec != 0)
	mproc[proc_nr].mp_flags |= ALARM_ON;	/* turn ALARM_ON bit on */
  else
	mproc[proc_nr].mp_flags &= ~ALARM_ON;	/* turn ALARM_ON bit off */

  /* Tell the clock task to provide a signal message when the time comes. */
  if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM);
  remaining = (int) m_sig.SECONDS_LEFT;
  return(remaining);
}

=========
fs/pipe.c
=========

/*===========================================================================*
 *				release					     *
 *===========================================================================*/
PUBLIC release(ip, call_nr, count)
register struct inode *ip;	/* inode of pipe */
int call_nr;			/* READ or WRITE */
int count;			/* max number of processes to release */
{
/* Check to see if any process is hanging on the pipe whose inode is in 'ip'.
 * If one is, and it was trying to perform the call indicated by 'call_nr'
 * (READ or WRITE), release it.
 */

  register struct fproc *rp;

  /* Search the proc table. */
  for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) {
	if (rp->fp_suspended == SUSPENDED 
	&& (rp->fp_fd & BYTE) == call_nr 
	&&  rp->fp_revived != REVIVING
	&&  rp->fp_filp[rp->fp_fd>>8]->filp_ino == ip) {
		revive(rp - fproc, 0);
		susp_count--;	/* keep track of who is suspended */
		if (--count == 0) return;
	}
  }
}

/*===========================================================================*
 *				do_unpause				     *
 *===========================================================================*/
PUBLIC int do_unpause()
{
/* A signal has been sent to a user who is paused on the file system.
 * Abort the system call with the EINTR error message.
 */

  register struct fproc *rfp;
  int proc_nr, task, susfd;
  struct filp *f;
  dev_nr dev;
  extern struct filp *get_filp();

  if (who > MM_PROC_NR) return(EPERM);
  proc_nr = pro;
  if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("unpause err 1", proc_nr);
  rfp = &fproc[proc_nr];
  if (rfp->fp_suspended == NOT_SUSPENDED) return(OK);
  task = -rfp->fp_task;

  if (task != XPIPE) {
	susfd = rfp->fp_fd >> 8;
	f = rfp->fp_filp[susfd];	/* don't use get_filp - fp is wrong */
	if (susfd > NR_FDS || susfd < 0 || f == NIL_FILP) {
		panic("unpause err 4", proc_nr);
	}
	dev = f->filp_ino->i_zone[0];	/* device on which proc is hanging */
	mess.TTY_LINE = (dev >> MINOR) & BYTE;
	mess.PROC_NR = proc_nr;
	mess.m_type = CANCEL;
	if (sendrec(task, &mess) != OK) panic("unpause err 2", NO_NUM);
	while (mess.REP_PROC_NR != proc_nr) {
		revive(mess.REP_PROC_NR, mess.REP_STATUS);
		if (receive(task, &m) != OK) panic("unpause err 3", NO_NUM);
	}
	revive(proc_nr, EINTR);	/* signal interrupted call */
  }

  return(OK);
}

	I haven't included my asynchronous tty driver, as I don't use IBM 
hardware and anybody interested in Kermit on Minix probably has their own
ideas. However, an outline of the driver follows for anybody who might be
interested (I'm happy to answer any further queries or comments as I 
continue this development).

	The tty driver is split into three parts:

tty.c		Contains the 'device independent' part of the original driver.
screen.c	Contains hardware-specific code for console screen and
		keyboard driver.
async.c		Contains hardware-specific code for asynchronous serial
		tty driver.

The tty_struct structure is extended, and now contains fields for ioctl
function addresses (so do_ioctl sets characters, modes etc then calls
the device - dependent code for baud rate, etc) and a single character
echo function for that port. Tty_ramqueue is a union of the integer queue 
required for screen driving and a char queue for buffering output, ready 
for async interrupts. The flag used for RUNNING / STOPPED indication is 
now a bit mask, another bit being STALLED, to indicate that an XOFF has 
been sent to the device on the other end of the serial line. 

TANDEM flowcontrol is implemented in tty.c, flipping the STALLED bit
and echoing an XOFF or XON when the buffer contents cross high and
low water marks. Interrupt processing is sufficiently fast that XOFF
does not need to be sent if the overrun queue is getting full.

Transmitter ready interrupts cause a character to be read from tty_rwords
and written to the UART. When tty_rwords is empty, a message is sent to
the tty task, and the queue is refilled. In the meantime, the transmitter
is disabled to avoid continuous interrupts.

Characters and refill requests are both sent through the same interrupt
message - use of the suggested TTY_O_DONE message would cause other
interrupts to be overwritten and lost. The message is now a structure 
consisting of a count, a queue of ints for character and line number,
and a bit map for refill requests. Thus refill requests are not lost
if the input queue is full. The kernel interrupt() call is only
made if the count of queued characters incremented from zero, or a bit
was set for the first time in the bitmap. This reduces the interrupt
processing time if an interrupt had already been sent, but had not been
processed.

A bug exists in the processing of input characters - DEL and QUIT send
a signal to the proper process only if it is the only login. The calculation
of the proc to be signalled as 'LOW_USER + line + 1' is wrong, since the 
process slot above the first user is occupied not by the second user's first
shell but by the update process. For proper processing of tty signals in 
a multiple-user (or multiple screen) system, FS should tell the tty task when 
a tty is controlling a process family.