[comp.unix.aux] autobauding getty

wmd@tippy (Malcolm Duncan) (03/28/90)

Following is a shell archive containing newgetty.c, a getty(8) replacement
programs that does automatic baud rate adjustment depending on result
codes sent by a Hayes Smartmodem(TM) 2400 modem attached to an A/UX
based Macintosh.

If there is sufficient interest, I have a short shell/awk script
to ensure that ghost getty's do not take a line out of service
due to unfinished background processes.  I understand that this
is a common problem with System V UNIX machines.  It certainly
is a problem on my A/UX box.

-------------------------------------------------------------------------
Malcolm Duncan            | Internet: wmd@tippy.cs.purdue.edu
Dir. Exec. Ed. Computing  | Duncan Communication Public Access UNIX BBS
Krannert School of Mgmt   | (317) 567-2143  24 hours, 300/1200/2400 
Purdue University         | Rt #1 Box 98E, Battle Ground, IN  47920



#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	newgetty.c
# This archive created: Tue Mar 27 11:10:17 1990
# By:	Malcolm Duncan (wmd@tippy.cs.purdue.edu)
export PATH; PATH=/bin:$PATH
if test -f 'newgetty.c'
then
	echo shar: will not over-write existing file "'newgetty.c'"
else
cat << \SHAR_EOF > 'newgetty.c'
/* FILE is newgetty.c  getty(8) program for use with any 2400 baud
                       Hayes Smartmodem(TM) 2400 compatable.

Copyright February 1990 by  W. Malcolm Duncan

	The author may be reached via any of the following means...

	US Mail:

	Duncan Communications BBS           Krannert Graduate School of Mgmt
	RR #1 Box 98E                and    KCTR 235, Purdue University
	Battle Ground, IN  47920            W. Lafayette, IN  47907


	Your favorite long distance service:

	Voice: (317) 494-7700 (office)      (317) 567-2220
	BBS:   (317) 567-2143 (modem)


	Electronic Mail:

	Internet:	wmd@tippy.cs.purdue.edu (always)
	AppleLink:	U0198 (often)
	Compuserve: 76012,1664 (seldom)


newgetty may be distributed freely AND may NOT be sold without permission
from the author.

Thanks to Paul Campbell from SuperMac Technologies for his help with the
SuperMac 4-port serial CommCard.

Thanks to Apple Computer, Inc. for providing no help whatsoever in the
development of newgetty despite numerous calls and pleas for assistance.
It's nice to know us little developers are important too!


Cable from Mac -> SmartModem 2400:

	Mac Mini DIN-8				RS-232 DB-25 Male
----------------------         -------------------
    Pin 1  Handshake Out           Pin 20 DTR
        2  Handshake In                 8 DCD
        3  Transmit Data -              2 TxD
        5  Receive Data -               3 RxD
        4  \_________Ground___________/ 1 GND
        8  /                          \ 7 GND

Caution, my abbreviations may be off but the pins are correct...


Modem Settings

Generally for any Hayes Smartmodem 2400 clone, we want to have the modem
hang up on drop of DTR; we use termio's hupcl setting to kill all on loss
of carrier (DCD) so we need to tell the modem to track the state of DCD; 
we want digital (not english) result codes from the modem to make the
program easier to write; we want a modem reset after loss of carrier
therefore our settings changes must be written into the modem's nonvolatile
RAM.  You'll have to do this by hooking the modem to a port and using cu(1C)
to talk to it and make the settings changes.

The Hayes(TM) commands to do this are as follows:

    ATV0      -- short form result codes
    AT&C1     -- DCD tracks carrier
    AT&D3     -- reset on DTR drop
    AT&W      -- write settings to nonvolitile RAM


Sample /etc/inittab entries

00:2:respawn:/etc/newgetty tty0 #Port tty0 (modem); set to "respawn" 
01:2:respawn:/etc/newgetty tty1 #Port tty1 (print); set to "respawn" 
a0:2:respawn:/etc/newgetty ttya0 #Port ttya0 (SuperMac 4 port board - port 1)
a1:2:respawn:/etc/newgetty ttya1 #Port ttya1 (SuperMac 4 port board - port 2)

As you can see, I placed my binary in the /etc directory.  I gave it the
same mode as getty, specifically -rwx------ or mode 0700 since init, being
a superuser process, can run anything it pleases.  See below...

-rwx------   1 root     sys        12364 Feb 26 21:26 /etc/newgetty


*/



#include	<stdio.h>
#include	<termio.h>
#include	<signal.h>
#include	<fcntl.h>


/* Define DEBUG for error logging */
#ifdef	DEBUG

FILE	*log;

#endif


/* variables used */

char			name[48];	/* login name typed to pass to login(1) */

int				input,		/* standard input file descriptor */
				output,		/* standard output file descriptor */
				error;		/* standard errput file descriptor */

struct termio	argp; 		/* tty settings buffer */


#define	TRUE	1
#define FALSE	0


main( argc, argv )
int	argc;
char	*argv[];
{
	register	int	i,				/* general use int */
					help,			/* help flag for login name loop */
					speed;			/* final speed to use */

	char			c,				/* general use char */
					ttyname[32];	/* user's tty */

	int				eof,			/* holder for user's end of file char */
					eol,			/* holder for user's end of line char */
					fd_count,		/* file descriptor count for select(2) */
					readmask,		/* file descriptor mask for select(2) */
					writemask,		/* file descriptor mask for select(2) */
					exceptmask;		/* file descriptor mask for select(2) */


	/* set up the port name */
	sprintf(ttyname,"/dev/%s", argv[1]);

	/* if it doesn't exist, bug out */
	if (access(ttyname, 6) != 0) exit(-1);
		

	close(0); close(1); close(2);	/* just in case */


	/* open the port and set up the appropriate file descriptors */
	input= open(ttyname, (O_RDWR + O_NDELAY));

	/* if this is a streams based tty line (SuperMac CommCard), push
       a line discipline.  If not, this call has no effect. */
	line_push(input);

	output= dup(0);
	error= dup(0);


#ifdef	DEBUG
	log= fopen("/tmp/gettylog", "a");

	fprintf(log, "newgetty starting\n");
	fflush(log);
#endif


	/* Now set port to highest modem baud rate with no echo, raw mode in
		anticipation of a carrier signal */

	i= ioctl(input, TCGETA, &argp);

#ifdef	DEBUG
	fprintf(log,"TCGETA ioctl returned %d\n", i);
	fflush(log);
#endif

	/* save original eof and eol characters */
	eof= argp.c_cc[VEOF];
	eol= argp.c_cc[VEOL];


	/* set up to read one char at a time with a timeout of
       255 tenths of a second */
	argp.c_cc[VEOF]= 1;
	argp.c_cc[VEOL]= 0xff;

	/* ignore breaks, no input preprocessing */
	argp.c_iflag= IGNBRK;

	/* no character post processing */
	argp.c_oflag= 0;

	/* 2400 baud, 8 bit characters */
	argp.c_cflag= B2400 + CREAD + CS8;
 
	/* straight raw i/o */
	argp.c_lflag= 0;

	/* set up the port */
	i= ioctl(input,TCSETA, &argp);


#ifdef	DEBUG
	fprintf(log,"TCSETA ioctl returned %d\n", i);
	fprintf(log, "Raw Mode set\n");
	fflush(log);
#endif


	/* want blocking i/o to prevent getty from chewing cpu cycles */
	i= ioctl(input,FIONBIO, 0);

#ifdef	DEBUG
	fprintf(log,"Blocking i/o ioctl returned %d\n", i);
	fflush(log);
#endif


	/* Now wait for a Smartmodem result code for carrier detect
		Code = '1' for 300/2400, '5' for 1200 carrier detect */

	c= 0;
	fd_count= 1;
	readmask= 1;
	writemask= 0;
	exceptmask= 0;

	do { 
		/* Wait for something to be available for reading.
			I use a select() call here to keep newgetty from
			munching CPU cycles in this loop waiting for character.
		*/
		select(fd_count, &readmask, &writemask, &exceptmask, 0);

		/* get a char from the modem and mask it down to 7 bits */
		i= read( input, &c, 1) ; 
		c &= 0x7f;

		/* no character, we must've timed out */
		if (i == 0) c= 0;

#ifdef	DEBUG
		else if (c != 0) {
			i= c;
			fprintf(log,"read an ASCII %d\n", i);
		}
#endif

	} while ((c != '1') && (c != '5')) ;


	/* Turn off raw mode and turn on echo and adjust baud rate if
		necessary */

	if (c == '5') { /* 1200 baud */

#ifdef	DEBUG
		fprintf(log, "Changing to 1200 baud\n");
		fflush(log);
#endif

		argp.c_cflag= B1200;
		argp.c_cflag|= CS8;
		argp.c_cflag|= CREAD;
		argp.c_cflag|= HUPCL;

		speed= 1200;
	}

	else { /* 300 or 2400 */

		/* get another character, 2400 baud result code is "10",
           300 baud result code is "1" followed by a return */

		while (read(input, &c, 1) <= 0) ;;
		c &= 0x7f;

		if (c != '0') {

			/* must be 300 baud, second char not a zero */

#ifdef	DEBUG
			fprintf(log, "Changing to 300 baud\n");
#endif

			argp.c_cflag= B300;
			argp.c_cflag|= CS8;
			argp.c_cflag|= CREAD;
			argp.c_cflag|= HUPCL;

			speed= 300;
		}

		else { /* 2400 baud */

			argp.c_cflag|= CS8;
			argp.c_cflag|= CREAD;
			argp.c_cflag|= HUPCL;

			speed= 2400;

#ifdef	DEBUG
			fprintf(log, "Staying at 2400 baud\n");
			fflush(log);
#endif
		}

	}

	/* wait for output to drain and set the new baud rate */
	i= ioctl(input,TCSETAF, &argp);

#ifdef	DEBUG
	fprintf(log,"speed TCSETA ioctl returned %d\n", i);
	fflush(log);
#endif


	/* reinstate modem DCD/DTR control on the line */
	i= ioctl(input, UIOCMODEM, 0);

#ifdef	DEBUG
	fprintf(log,"UIOCMODEM ioctl returned %d\n", i);
	fprintf(log, "Modem control set\n");
	fflush(log);
#endif


	/* flush everything */
	ioctl(input,TCFLSH,2);
	ioctl(output,TCFLSH,2);


	/* let the line settle, a little longer for 300 baud */
	sleep(1);
	if (speed == 300) sleep(1);


	/* give em 60 seconds to get a name typed in */
	alarm(60);
	signal(SIGALRM, SIG_DFL);


	/* loop until we get a login name, or timeout */
	help= TRUE;
	while( help ) {

		help= FALSE;
		
		/* Get the user's name and keep asking until he types one
			or if line errors have caused a > 8 char name */

		do {

			/* enter your prompt here using my print() routine:
				remember to enter both carriage returns AND line feeds in
				multi-line prompts as shown below...
			*/

			print("\r\n\n\nThank you for calling the Duncan Communications BBS!\n\n");
			print("\rType in your account name or HELP and press Return or Enter.\n");
			print("\rUsers without an account should enter 'user' as an account name.\n\n\rlogin: ");

			ioctl(input,TCFLSH,0);	/* flush input before reading */
			i= mygets(name, 40);

		} while ((i == 0) || (i > 8));
	

		if (strcmp(name,"help")==0) {

			/* if he wants help, give it to him and give him two minutes
               to get his name typed in */

			alarm(120);

			loginhelp();
			help= TRUE;
		}

	}
	
	/* enable echo, erase and kill processing, etc. */
	argp.c_lflag= ISIG;
	argp.c_lflag|= ICANON;
	argp.c_lflag|= ECHO;
	argp.c_lflag|= ECHOE;
	argp.c_lflag|= ECHOK;

	/* enable input preprocessing */
	argp.c_iflag|= IGNBRK;
	argp.c_iflag|= ISTRIP;
	argp.c_iflag|= ICRNL;
	argp.c_iflag|= IXON;

	/* enable output postprocessing */
	argp.c_oflag|= OPOST;
	argp.c_oflag|= ONLCR;

	/* restore eof, eol and erase characters */
	argp.c_cc[VEOF]= eof;
	argp.c_cc[VEOL]= eol;
	argp.c_cc[VERASE]= 0x08; /* ASCII BS */

	/* set up the port for login */
	i= ioctl(input,TCSETA, &argp);


#ifdef	DEBUG
	fprintf(log,"last TCSETA ioctl returned %d\n", i);
	fflush(log);
#endif


#ifdef	DEBUG
	fprintf(log,"launching login\n");
	fclose(log);
#endif

	/* call login */
	execl("/bin/login", "login", name, 0);
}


loginhelp()
{
	char	tmp[12];


	/* Use my print() routine and remember to put BOTH carriage returns
		and line feeds in the strings. */

print("\r\n\n\nWelcome to the Duncan Communications Bulletin Board System!\n\n");

print("\rEach user of this bulletin board has an account name and\n");
print("\rpassword he or she uses to 'log in' to the system.\n");
print("\rThe system is particular in that it wants all account\n");
print("\rnames entered in lower case.  Please watch out for this.\n\n");

print("\rShould you be new to the Duncan BBS, welcome!  You can use the\n");
print("\rguest account 'user' (without the quotes).  No password will be\n");
print("\rrequired.  If you find the system to be of sufficient interest\n");
print("\rthat you may want an account of your own, you will be able to\n");
print("\rapply for one using this guest account.\n\n");

print("\rPlease press return or enter...");
	mygets(tmp, 10);
}


/* print() writes a null-terminated string to the output file desctiptor */

print( string )
char	*string;
{
	register	char	*cptr;

	cptr= string;

	while (*cptr != 0)
		write(output, cptr++, 1);
}



/* get a string from the input file descriptor.  The string's length cannot
	exceed the maxLength parameter. */

int	mygets( string, maxLength )
char	*string;
int		maxLength;
{
	register	char	*endptr, *cptr;

	register	int	i;

	char		eraseString[4];


	/* set up eraseString as BACKSPACE-SPACE-BACKSPACE */
	eraseString[0]= 0x08;
	eraseString[1]= ' ';
	eraseString[0]= 0x08;
	eraseString[0]= 0;

	/* endptr is used for comparison */
	endptr= string;
	endptr += maxLength;

	cptr= string;
	*cptr= 0;

	/* get a user-typed char */
	i= read(input, cptr, 1);

	do {
		/* strip any parity bits, newgetty ignores parity */
		*cptr &= 0x7F;


		/* if i <= 0 then
			we didn't get a character as a result of the read() */

		if (i>0) switch	(*cptr) {

			/* backspace and delete are acceptable backspacing characters */
			case 0x08:
			case 0x7f:

				/* see if there IS a previous char */
				if (cptr > string) {

					/* erase it */
					print(eraseString);
					cptr--;

				}
				break;


			/* both line feed and carriage returns are acceptable line
				termination characters */
			case 10:
			case 13:

				*cptr= 0;

				/* remember to echo BOTH a line feed and a carriage return */
				print("\r\n");

				/* return the length of the string typed. */
				return(strlen(string));
				break;


			default:

				/* ignore any other control characters */
				if (*cptr < ' ') break;

				/* convert uppcase letters to lower case */
				if ((*cptr>='A')&&(*cptr<='Z'))
					*cptr+= 'a' - 'A';

				/* echo the character */
				write(output, cptr, 1);
				cptr++;

				if (cptr == endptr) {	/* length limit reached */

					cptr--;	/* back up and null terminate the string */
					*cptr= 0;

					print("\r\n");
					return(strlen(string));
				}

				break;
		}

		/* get a user-typed char */
		i= read(input, cptr, 1);

	} while (TRUE);

}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0



--
-------------------------------------------------------------------------
Malcolm Duncan            | Internet: wmd@tippy.cs.purdue.edu
Dir. Exec. Ed. Computing  | Duncan Communication Public Access UNIX BBS
Krannert School of Mgmt   | (317) 567-2143  24 hours, 300/1200/2400 
Purdue University         | Rt #1 Box 98E, Battle Ground, IN  47920