[alt.sources] BETA RELEASE: new version of "write"

taylor@limbo.Intuitive.Com (Dave Taylor) (11/14/89)

The enclosed is a new version of the "write" program that I have
written to smooth some of the rough interface and algorithm edges
in the original.  

This is currently a bit HP-UX specific in its emulation of the (much
nicer) BSD tty driver.  It should prove to be pretty portable though, 
and if you read the notes included, you'll see it's easy to build for 
other machines.

Highlights of the new program include:

	o  An "echo" character-by-character transmission mode
	   a la the popular 'talk' program.

	o  A sophisticated algorithm for finding the best
	   line to connect to, rather than just the first
	   in the /etc/utmp file.

	o  Some nifty starting options for added functionality.

In summary, it isn't a dramatic new rewrite, but rather a nice
new version (written from scratch, for those of you nervous about 
the origin of commands) that adds some needed functionality.

THIS IS A BETA VERSION in the sense that I've only ever run it on
my HP-UX 9000/300 computer running HP-UX release 6.5.  I make no
guarantee that it'll run on any other platform, but I encourage
people to try it and make any changes necessary to have it work on
your local platform.  If there's enough interest/change I would 
like to submit this to "comp.sources.unix" too at some point.

			Enjoy!
						-- Dave Taylor
Intuitive Systems
Mountain View, California

taylor@limbo.intuitive.com    or   {uunet!}{decwrl,apple}!limbo!taylor

-- Attachment:

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by Dave Taylor <taylor@limbo> on Mon Nov 13 12:40:33 1989
#
# This archive contains:
#	README	write.1	write.c	
#
# Error checking via wc(1) will be performed.
# Error checking via sum(1) will be performed.

PATH=/bin:/usr/bin:$PATH; export PATH

if sum -r </dev/null >/dev/null 2>&1
then
	sumopt='-r'
else
	sumopt=''
fi

echo x - README
cat >README <<'@EOF'
This is a new version of the old Unix standard "write" program
intended to improve on some of the rough interface edges of the
original.  It is currently somewhat wired to an HP computer running
HP-UX, however, and might be a bit funky to run on other machines.

To compile the program, simply:

	cc -O write.c -o write

or, if you're on a BSD machine:

	cc -O -DBSD write.c -o write

When it's compiled, simply install it in your favorite shared
directory, and copy the man page into your local man directory.

Bugs, ideas, suggestions, hate mail, etc, please send to me
directly at:   taylor@limbo.intuitive.com

						-- Dave Taylor

Nov 12, 1989
@EOF
set `sum $sumopt <README`; if test $1 -ne 57913
then
	echo ERROR: README checksum is $1 should be 57913
fi
set `wc -lwc <README`
if test $1$2$3 != 22111650
then
	echo ERROR: wc results of README are $* should be 22 111 650
fi

chmod 644 README

echo x - write.1
cat >write.1 <<'@EOF'
.TH WRITE 1L "" "" 
.SH NAME
write \- interactively talk with another user
.SH SYNOPSIS
.B write 
[
.I\ -c
]
.I user
.sp
.B write 
[
.I \-et
] [
.I "-l line"
]
.I user 
.SH DESCRIPTION
This new version of the traditional Unix
.I write 
program copies lines from your terminal to another user
on the computer.  
.PP
Unlike the original program, however, this can let you interact 
on a character-by-character basis, as well as line-by-line.  Also, 
the algorithm for choosing which of multiple login sessions has 
greatly improved, with the program now choosing the 
most-recently-active of the interactive sessions the "writee" is 
connected to.
.PP
A request from the 
.I write
program appears similar to the following:
.PP
.RS
Write requested by Dave Taylor at Monday, Nov 13, 1989 at 11:48 am
\t(to respond, please type "write -e taylor" on the command line)
.RE
.PP
Notice that the indication of how to respond also indicates whether
or not the person requesting a 
.I write
with you chose to use the (\fIecho\fR) character-by-character
transmission mode: the presence of
.I \-e
indicates that they did.
.PP
When you have successfully connected to the other users terminal
line, the program indicates that by informing you similar to:
.PP
.RS
Okay, you're connected to user taylor.  Enter text to send, ^D to quit
.RE
.PP
And upon termination of the connection, both sides see the
indication 
.I "<end-of-transmission>"
for each person.
.PP
If you don't want people to bother you with 
.I write
or
.I talk
requests, use the
.I mesg(1)
command to disable writing to your terminal.
.PP
Since communication of this nature can be quite confusing
and jumbled, it is suggested that people stick with the
protocol used by radio operators, with "over" and "over
and out" being used to indicate the end of a transmission
and the end of the session, respectively.  Most commonly,
these are abbreviated "-o" for "over", and "-oo" for "over
and out".
.SH OPTIONS
The 
.I write
program has the following set of options available:
.TP
.B "-c"
Check for user sessions.  This option is an easy way
to ascertain if the requested user is logged in, and if so
which of their possibly multiple sessions are active.  
Output is of the form:
.RS

.in +.25i
For user "\fIusername\fR":
.br
.in +.25i
Line tty1 is active
.in -.50i
.RE
.TP
.I " "
and upon completion of the output, the
.I write
command quits (e.g. it does not send a message to the
other user or otherwise do any of the normal work of
the program).
.TP
.B "-e"
Puts the 
.I write
program into 
.I echo
or character-by-character transmission mode.  In this mode the
person on the other end can see what you're typing, character
by character, rather than having to wait for you to complete
an entire line.  This is the recommended, though not default,
way of interacting with another user.
.TP
.B "-l" \fIline\fR
If you want to connect to a specific line instead of letting
the 
.I write
program use the builtin algorithm for ascertaining the best
line to connect, you can specify the device name
with this option.
.TP
.B "-t"
If you'd rather that the person you are requesting the talk from
isn't informed of what tty line you're on (which is almost always
unnecessary now since the program can pick the most active session,
which, if you've just started a 
.I write
is most likely to be the right one).
.SH EXAMPLES
Let's say that we want to talk to our friend Scott.  We can
easily do this by typing:
.IP
.B write scott
.P
Which will result in Scott seeing a message similar to that
shown above.  As soon as we've connected, we'll get a message
from the 
.I write
program to that effect.  At this point
.I "anything we type will be displayed on Scott's"
.I "terminal too,"
so we need to be careful!  It's usually best just to sit and wait for a
minute or two to give the other party time to reply.
.PP
Scott chooses to connect to us by using the
.I write 
command, and so we promptly see:
.PP
.RS
Write requested by Scott McGregor at Monday, Nov 13, 1989 at 11:53 am
\t(to respond, please type "write -e scott" on the command line)
.RE
.PP
which indicates that, since we're already hooked up to him, the
two way communication is established.  Scott says hi by typing
it on his keyboard, and we can imagine the following 
interaction (with Scott's typing in italics):
.PP
.in +.25i
.nf
.I "Hi Dave!  How's it going?   -o"
Not bad.  How about with you?  -o
.I "Quite well.  Real busy though.  Can we talk later?  -o"
sure.  sorry for the interruption. -oo
.I "-oo"
<end of transmission>
.fi
.in -.25i
.SH SEE ALSO
mail(1),
mesg(1),
talk(1).
.SH AUTHOR
Dave Taylor, Intuitive Systems
.br
<taylor@limbo.Intuitive.Com>
.SH NOTES
This program uses a modified 
.I gets(3)
routine to do raw mode reads, and might well be HP-UX specific.
If you're not on an HP computer, the behaviour of this routine
might be most peculiar...
.PP
People on HP computers, however, may realize while in this program
that it offers the functionality of the BSD tty driver, to wit the
use of "^W" to erase a word, and "^R" to rewrite the line again.
Why they're not part of the standard System V tty driver...
.PP
This program does, however, run on any computer; if you're not on
one that understands the HP-UX 'raw mode' magic, then simply compile
it with "-DBSD" to use your standard system 
.I "gets()" 
routine.
.PP
I am indebted to Marvin Raab for suggesting that this type of a 
.I write 
program would be a nice addition to the system, and to Jim
Davis for testing it out on some other hardware.
@EOF
set `sum $sumopt <write.1`; if test $1 -ne 6381
then
	echo ERROR: write.1 checksum is $1 should be 6381
fi
set `wc -lwc <write.1`
if test $1$2$3 != 1979815520
then
	echo ERROR: wc results of write.1 are $* should be 197 981 5520
fi

chmod 644 write.1

echo x - write.c
cat >write.c <<'@EOF'
/**				write.c				**/

/** A new version of the Unix "write" utility that takes advantage of some
    local environment changes.  Most notable change, other than it being
    friendlier and easier to use, is that when someone is logged in more 
    than once it will connect to the window with the least idle time (that 
    isn't "mesg n") rather than just blindly the first in the /etc/utmp file.

    (C) Copyright 1989 Dave Taylor
    (C) Copyright 1989 Intuitive Systems

    All rights reserved.

    Possible environment options: 
	BSD		to attempt to work in the BSD environment
**/

#include <sys/types.h>
#include <sys/stat.h>
#include <utmp.h>
#include <pwd.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>

#ifndef TRUE
# define TRUE			1
# define FALSE			0
#endif

#define MAX_LOGINS		30		/* max for an individual user */
#define SLEN			256

#define DEVICE_NAME_TEMPLATE 	"/dev/%s"

/** forward declarations **/

long	get_idle();
char   *get_best_line(), *current_time(), 
       *getenv(), *ttyname(), *readline();
int     compare();

/** static data buffers **/

char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
		   "Sep", "Oct", "Nov", "Dec" };
char *days[]   = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
		   "Friday", "Saturday" };

/** global variables **/

struct login_entry {
	char	line[20];
	long	idle;
       } login_record[MAX_LOGINS];

FILE    *remote_fd;			/* the other persons (/dev) tty line  */
char    requested_user[20],		/* whom we want to talk with	      */
	remote_line[40] = { "" },	/*  .. what tty line they're on?      */
	who_we_are[20],			/* who we are, login/session name     */
	fullname[40];			/*  .. our full name from /etc/passwd */
char    our_tty_line[20];		/*  .. and what tty line we're on     */

int     sessions = 0,			/* number of sessions user is running */
	raw_echo = 0,			/* echo char by char or line by line? */
	show_our_tty_line = 0,		/* tell them our tty line?            */
	show_user_idle_time_only = 0;	/* only show user idle times?  	      */

extern int   optind;			/* these are for the getopt() routine */
extern char *optarg;			/*   .. used in main() below          */

main(argc, argv)
int    argc;
char **argv;
{
	struct utmp *utmp_buffer, *getutent();
	struct passwd *pwentry,   *getpwnam();
	char    *line_to_use, *cp, c;
	long     idle_time;
	int      unreachable_login = 0;

	/** deal with the starting arguments **/

	while ((c = getopt(argc, argv, "cel:t")) != EOF) {
	  switch (c) {
	    case 'c' : show_user_idle_time_only = TRUE;	break;
	    case 'e' : raw_echo = TRUE;			break;
	    case 'l' : strcpy(remote_line, optarg);	break;
	    case 't' : show_our_tty_line = TRUE;	break;
	    default  : usage(0);			break;
	  }
	}

	if (optind == argc) usage(0);

	strcpy(requested_user, argv[optind]);

	/** now let's go into the UTMP file and load our data structure **/

	while ((utmp_buffer = getutent()) != NULL) {
	  if (utmp_buffer->ut_type != USER_PROCESS || 
	      strcmp(utmp_buffer->ut_name, requested_user) != 0)
	    continue;

	  /* make sure there is some reasonable idle time (e.g. no error) */
  
	  if ((idle_time = get_idle(utmp_buffer->ut_line)) != (long) -1) {
	    strcpy(login_record[sessions].line, utmp_buffer->ut_line);
	    login_record[sessions++].idle     = idle_time;
	  }
	  else unreachable_login++;
	}
	endutent();

  	/** if we can't find the lad or lass, then ... **/

	if (sessions == 0) {
	  if (unreachable_login)
	    printf("Sorry, but user %s is not currently allowing messages.\n",
		   requested_user);
	  else
	    printf("Sorry, but user '%s' is not currently logged in.\n",
		    requested_user);
	  exit(1);
	}
 
	/** figure out who we are and what line we're on... **/

	if ((cp = getenv("LOGNAME")) != NULL)
	  strcpy(who_we_are, cp);
	else if ((cp = getenv("USER")) != NULL)
	  strcpy(who_we_are, cp);
	else {
	  fprintf(stderr, "Can't figure out who you are.  Sorry...\n");
	  exit(1);
	}

	/* get our full name from the password file */

	if ((pwentry = getpwnam(who_we_are)) == NULL)
	  strcpy(fullname, who_we_are);
	else
	  strcpy(fullname, pwentry->pw_gecos);

	/* and what tty line we're on */

	if ((cp = ttyname(0)) == NULL) {
	  fprintf(stderr, "Can't figure out what line you're on.  Sorry...\n");
	  exit(1);
	}
	else
	  strcpy(our_tty_line, cp);

	/** otherwise, let's get the best line to try them on... **/

	line_to_use = get_best_line();

	/** now let's hit that line and go wild... **/

	if (! show_user_idle_time_only)
	  initiate_and_run_write_session(line_to_use);

	/** and we're done. **/

	exit(0);
}

initiate_and_run_write_session(line)
char *line;
{
	/** this does all the actual work associated with the transfer of
	    information from the local user to the remote, including opening
	    up the line and spitting out the startup banner.
	**/

	char  filename[40], input_line[SLEN];
	int   exit_gracefully(), signal_restarting();
	
	sprintf(filename, DEVICE_NAME_TEMPLATE, line);

	if ((remote_fd = fopen(filename, "w")) == NULL) {
	  fprintf(stderr, "Internal error from 'write': can't write to '%s'\n",
		  filename);
	  exit(1);
	}

	/** the hello banner... **/
	
	fprintf(remote_fd, "\n\r\n\rWrite requested by %s at %s\n\r", fullname, 
		  current_time());

	if (show_our_tty_line)
	  fprintf(remote_fd,
   "\t(to respond, type \"write %s-l %s %s\" on the command line)\n\r\n\r",
		raw_echo ? "-e " : "", (char *) our_tty_line + 5, who_we_are);
	else
	  fprintf(remote_fd,
      "\t(to respond, please type \"write %s%s\" on the command line)\n\r\n\r",
		raw_echo ? "-e " : "", who_we_are);

	printf(
       "Okay, you're connected to user %s.  Enter text to send, ^D to quit\n\n",
		requested_user);

	/** and now, the main loop... **/

	(void) signal(SIGQUIT, exit_gracefully);
	(void) signal(SIGINT,  exit_gracefully);
	(void) signal(SIGCONT, signal_restarting);

	while (readline(input_line) != NULL) {
	  if (! raw_echo)
	    fprintf(remote_fd, "%s\n\r", input_line);
	}

	fprintf(remote_fd, "<end-of-transmission>\n\r");
}

char *
get_best_line()
{
	/** use the information we've gained to hit the best of the
	    possible lines.  Return its name at this point ... and 
	    if the user specified a tty line, try to use that instead
	    or fail if not a good combo.
	**/

	register int i, matched = 0;

	/** 1. sort information by idle time so newest is up top **/

	qsort(login_record, sessions, sizeof(login_record[0]), compare);

	/** then show what we've gotten so far, shall we? **/

	if (show_user_idle_time_only)	/* if they're interested */
	  show_idle_times();

	/** if the user specified a tty line, let's see if the 	
	    person we want is *on* that line... **/

	if (remote_line[0] != '\0') {
	  for (i=0; i < sessions; i++) {
	   if (strcmp(remote_line, login_record[i].line) == 0)
	     matched++;
	  }
	  if (! matched) { 			 /* nope! */
	    printf("User '%s' doesn't appear to be logged in to line '%s'.\n",
		   requested_user, remote_line);
	    exit(1);
	  }
	  return( (char *) remote_line );	/* yep! */
	}

	/** othrewise simply return the top record, or least idle line **/

	return( (char *) login_record[0].line );
}

show_idle_times()
{
	register int i, hours;

	printf("\nFor user '%s':\n", requested_user);

	for (i = 0 ; i < sessions; i++) {
	  if (login_record[i].idle < 10)
	    printf("\tline %s is active\n", login_record[i].line);
	  else if (login_record[i].idle < 60)
	    printf("\tline %s has %d seconds idle time\n",
		   login_record[i].line, login_record[i].idle);
	  else if (login_record[i].idle < 3600)
	    printf("\tline %s has %d minutes idle time\n",
		  login_record[i].line, (login_record[i].idle / 60));
	  else {
	    hours = login_record[i].idle / 3600;
	    printf("\tline %s has %d hours and %d minutes idle time\n",
		  login_record[i].line,  hours,
		  (login_record[i].idle - (hours * 3600)) / 60);
	  }
	}

	putchar('\n');
}

long
get_idle(line)
char *line;
{
	/** Given the name of a tty line, get the idle time for that line
	    by doing a stat on that device and comparing it with the current
	    time.  Returns the difference between the two...

	    Slightly more useful level of sophistication added: this routine
	    will also check the permissions on the line and will return (-1)
	    if it does *NOT* have write permission to group and other (which
	    indicates that "mesg n" has been typed for that window/line)
	**/

	long thetime;
	char filename[80];
	struct stat statbuffer;

	thetime = time( (long *) 0);

	sprintf(filename, DEVICE_NAME_TEMPLATE, line);
	
	if (stat(filename, &statbuffer) != 0) 
	  return( (long) -1);

	if (((statbuffer.st_mode & S_IWGRP) == 0) || 
	    ((statbuffer.st_mode & S_IWOTH) == 0))
	  return( (long) -1);	/* user doesn't have messages enabled! */
	else
	  return( thetime - statbuffer.st_atime );
}

int
compare(a, b)
struct login_entry *a, *b;
{
	/** compare two login structures for qsort. Returns a similar
	    value to what strcmp() would return, only in this we are
	    interested in idle time, not names...
	**/

	return ( (int) (a->idle - b->idle) );
}

exit_gracefully()
{
	/** to more pleasantly deal with the user quitting with ^C **/

	if (raw_echo > 1) 
	  rawmode(0);

	fprintf(remote_fd, "\n\r<end-of-session>\n\r");
	printf("<left with an interrupt.  Goodbye!>\n");

	exit(0);
}

signal_restarting()
{
	int signal_restarting();

	if (raw_echo > 1) { 
	  rawmode(0);
	  rawmode(1);	/* force it to be turned on regardless of mode */
	  printf("<back!>\n");
	}

	(void) signal(SIGCONT, signal_restarting);
}

char   *
current_time()
{
	/** return a string containing the current date and time, but
   	    in a more readable format that what ctime() returns.
	**/

	long 	thetime;
	struct tm *timerec, *localtime();
	static char buffer[40];

	thetime = time( (long *) 0);

	/** now let's get that into a ctime structure... **/

	timerec = localtime(&thetime);

	sprintf(buffer, "%s, %s %d, 19%d at %d:%.2d %s",
			days[timerec->tm_wday],
			months[timerec->tm_mon],
			timerec->tm_mday,
			timerec->tm_year,
			(timerec->tm_hour > 12 ? timerec->tm_hour - 12 :
						 timerec->tm_hour),
			timerec->tm_min,
			(timerec->tm_hour > 11 ? "pm" : "am")
		);

	return( ( char *) buffer);
}

usage(exit_value)
int exit_value;
{
	/** This outputs a friendly and informative usage message. **/

	printf("\nUsage: write [-c] [-e] [-l line] [-t] username\n\n");
	printf("Where:\n");
	printf(
	   "\t-c   \trequests a simple check of that user: does not connect\n");
	printf(
	   "\t-e   \techo (transmit) char-by-char instead of line-by-line\n");
	printf("\t-l X \tconnects to the specified user on tty line 'X'\n");
	printf(
	   "\t-t   \tspecifies that the remote user should be informed of\n");
	printf(
      "\t      \twhich tty line you're logged in to (default is least idle)\n");
	printf(
"\nThis version of \"write\" ensures that you will always connect to the tty\n"
	      );
	printf(
 "line with the least idle time, if the specified user is logged in to more\n");
	printf(
 "than one line.  Therefore, the default is not to even show tty lines on\n");
	printf("connect...\n\n");

	exit(exit_value);
}

/****** The following code is ripped out of the RAYS software package ******/

/**			readline.c			**/

/** This routine is used to emulate the "gets()" routine as if we had 
    the BSD tty driver that knows about ^W word deletion and ^X line
    deletion...on machines that already have this we don't need it. so

    (C) Copyright 1987, Dave Taylor
    (C) Copyright 1987, Hewlett-Packard Laboratories
**/

#ifndef BSD

#include <termio.h>

#define output_char(c)		{					       \
				  if (raw_echo) {			       \
				    if (c == '\n') putc('\r', remote_fd);      \
				    putc(c, remote_fd);  fflush(remote_fd);    \
				  }					       \
				  putchar(c);				       \
				}
#define isspace(c)		(c == ' ' || c == '\t')
#define isstopchar(c)		(c == ' ' || c == '\t' || c == '/')
#define ctrl(c)			(c - 'A' + 1)
#define erase_a_char()		{ output_char(BACKSPACE); output_char(' '); \
			          output_char(BACKSPACE); fflush(stdout); }
#define TTYIN			0
#define BACKSPACE		'\b'

#ifndef TRUE
# define TRUE			1
# define FALSE			0
#endif

#define ON			1
#define OFF			0

static int _in_rawmode = FALSE;

struct termio _raw_tty, 
              _original_tty;

char *readline(buffer)
char *buffer;
{
	/** this routine understands ^W to delete back to a stopchar,
	    ^X or ^U to delete the entire line, ^R to request a rewrite
	    of the line so far, and ^M/^J to end the input line.  This
	    routine returns either a pointer to the line input or a NULL
	    if the user hit a ^D or we otherwise got an EOF(stdin).
    	    Finally, we also hop in and out of raw mode in this routine,
	    thereby mucking with the terminal tty settings...
	**/

	char ch;
	register int index = 0;

	if (raw_echo++ == 1) 	/* once it's on, we'll leave it on ... */
	  rawmode(ON);

	do {
	  ch = getchar();

	  if (ch == ctrl('D') || feof(stdin)) {		/* we've hit EOF */
	    rawmode(OFF);
	    output_char('\n');
	    return((char *) NULL);
	  }

	  switch (ch) {

	    case BACKSPACE: if (index > 0) {
	      		      output_char(BACKSPACE);
  	      		      index--;
	    		    }
	    		    output_char(' ');
	    		    output_char(BACKSPACE);
            		    fflush(stdout);
	   		    break;
 
	    case '\n'     :
	    case '\r'     : buffer[index] = '\0';
			    if (! raw_echo)
			      rawmode(OFF);
			    output_char('\n');
	    		    return((char *) buffer);

	    case ctrl('W'): if (index == 0)
		  	      break;			/* nothing to do */

			    index--;

			    if (buffer[index] == '/') {	/* special case. */
			      erase_a_char();
			      break;
			    }

			    while (index >= 0 && isspace(buffer[index])) {
			      index--;
			      erase_a_char();
			    }

			    while (index >= 0 && ! isstopchar(buffer[index])) {
	      		      index--;
	      		      erase_a_char();
	    		    }
	    		    index++;
			    break;

	    case ctrl('R'): buffer[index] = '\0';
			    printf("\n%s", buffer);	
			    break;

	    case ctrl('X'):
	    case ctrl('U'): while (index>0) {
	      		      index--;
	      		      erase_a_char();
	    		    }
		            index = 0;
			    break;
	    default       :

			    if (ch > 0)	{  	/* job control quirk... */
			      buffer[index++] = ch;
			      output_char(ch);
	  		    }
	  }

	} while (index < SLEN);

	buffer[index] = '\0';

	output_char('\n');
	return((char *) buffer);
}


rawmode(state)
int state;
{
	/** state is either ON or OFF, as indicated by call **/

	if (state == OFF && _in_rawmode) {
	  (void) ioctl(TTYIN, TCSETAW, &_original_tty);
	  _in_rawmode = 0;
	}
	else if (state == ON && ! _in_rawmode) {

	  (void) ioctl(TTYIN, TCGETA, &_original_tty);	/** current setting **/

	  (void) ioctl(TTYIN, TCGETA, &_raw_tty);    /** again! **/
	  _raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode        */

	  _raw_tty.c_cc[VMIN] = '\01';	/* minimum # of chars to queue    */
	  _raw_tty.c_cc[VTIME] = '\0';	/* minimum time to wait for input */

	  (void) ioctl(TTYIN, TCSETAW, &_raw_tty);

	  _in_rawmode = 1;
	}
}

#endif
@EOF
set `sum $sumopt <write.c`; if test $1 -ne 50356
then
	echo ERROR: write.c checksum is $1 should be 50356
fi
set `wc -lwc <write.c`
if test $1$2$3 != 580205815148
then
	echo ERROR: wc results of write.c are $* should be 580 2058 15148
fi

chmod 644 write.c

exit 0