This is runscript.c in view mode; [Download] [Up]
/*
** Parse and execute dialing script. Ancient history says this was
** based on MMDF, but it wouldn't be recognized now...
** Copyright (c) 1991 Bolt Beranek and Newman, Inc.
** All rights reserved.
**
** Redistribution and use in source and binary forms are permitted
** provided that: (1) source distributions retain this entire copyright
** notice and comment, and (2) distributions including binaries display
** the following acknowledgement: ``This product includes software
** developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the
** documentation or other materials provided with the distribution and in
** all advertising materials mentioning features or use of this software.
** Neither the name of Bolt Beranek and Newman nor CREN/CSNET may be used
** to endorse or promote products derived from this software without
** specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
** WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include "diald.h"
#include "dialupip.h"
/* Various constants. */
#define MAXFILES 15
#define MAXLINE 256
#define MAXFIELDS 10 /* Maximum number of parameters */
#define MATCHLEN 128 /* Size of text to match in recv */
#define XMITWAIT 60 /* How long to wait on a write */
#define REPBUFSIZE 256 /* Size of replay buffer */
#define SC_SLEEP 0377 /* Char for \x, sleep */
#define SC_BREAK 0376 /* Char for \#, break */
/* Command codes; must not overlap with D_xxx codes */
#define S_ABORT 101 /* Abort the script */
#define S_ALT 102 /* Alternate */
#define S_COMMENT 103 /* Comment */
#define S_GO 104 /* Start the protocol */
#define S_LOG 105 /* Log a message */
#define S_MARK 106 /* Mark the place to start a replay */
#define S_RECV 107 /* Watch for a string */
#define S_REPLAY 108 /* Reuse old input */
#define S_SELEND 109 /* Select end */
#define S_SELST 110 /* Select begin */
#define S_USEFILE 111 /* Read script commands from another file */
#define S_XMIT 112 /* Transmit a string */
#define S_EOF 113 /* EOF in file */
/* Error codes return by commands */
#define D_CONTIN 2 /* Continue with process */
#define D_OK 1 /* Everything is fine */
#define D_NOMATCH 0 /* Input did not match */
#define D_FATAL -1 /* Fatal error */
#define D_NONFATAL -2 /* Possibly-recoverable error */
#define D_INTRPT -3 /* Interrupt during system call */
/* Error codes returned by parsing routines */
#define ERR_NOWORD -1
#define ERR_QUOTE -2
#define ERR_FIELDS -3
#define ERR_BADVAR -4
#define ERR_TOOLONG -5
/* Structure that keeps track of open files. */
typedef struct _OPENFILE {
char *scriptname;
FILE *scriptfile;
int linenumber;
int nfields;
char *fields[MAXFIELDS];
} OPENFILE;
/* Structure that defines a command */
typedef struct _COMMAND {
char *Name;
int Code;
int MinFields;
int MaxFields;
} COMMAND;
static char scriptname[128]; /* Script filename */
static char pushbuffer[MATCHLEN + 2]; /* Pushback buffer for matcher */
static char *pushptr = pushbuffer; /* Pointer into buffer */
static jmp_buf timerest;
static int linenumber; /* Line number in script file */
static int use_nfields; /* Number of "use" fields */
static char *use_fields[MAXFIELDS]; /* Current "use" fields */
static OPENFILE *openfiles[MAXFILES]; /* Stack of open files */
static int nopenfiles; /* Number of open files */
static FILE *scriptfile; /* Script file */
static FILE *portfile; /* Modem file */
static FILE *tranfile; /* Transcript file descriptor */
static int transtyle; /* Transcript style */
static int repcount; /* Chars left in replay */
static int repcurr; /* Current replay char */
static int repnext; /* Where to insert next char */
static int repsize; /* Replay buffer size; constant */
static int repfirst; /* First char in replay */
static int replay; /* Doing a replay? */
static int repbuff[REPBUFSIZE]; /* Replay buffer */
static char WHERE[] = "dialscript"; /* Where we are, for logging */
static COMMAND commands[] = {
{ "abort", S_ABORT, 0, 0 },
{ "alternate", S_ALT, 0, 0 },
{ "go", S_GO, 0, 0 },
{ "log", S_LOG, 1, 1 },
{ "mark", S_MARK, 0, 0 },
{ "recv", S_RECV, 2, 2 },
{ "replay", S_REPLAY, 0, 0 },
{ "use", S_USEFILE, 1, 10 },
{ "xmit", S_XMIT, 1, 1 },
{ "{", S_SELST, 0, 0 },
{ "}", S_SELEND, 0, 0 },
{ NULL }
};
extern char *strerror();
/*
** Report a syntax error.
*/
static void
syntax_error(problem)
char *problem;
{
d_log(DLOG_GENERAL, WHERE, "Syntax error in \"%s\", line %d:\n\t%s\n",
scriptname, linenumber, problem);
}
/*
** Report an I/O error.
*/
static void
io_error(problem)
char *problem;
{
d_log(DLOG_GENERAL, WHERE, "I/O error in \"%s\", line %d:\n\t%s\n",
scriptname, linenumber, problem);
}
/*
** Report a miscellaneous error with errno.
*/
static void
misc_error(format, arg)
char *format;
char *arg;
{
char buff[256];
(void)sprintf(buff, "Error in \"%s\", line %d:\n\t%s, %s",
scriptname, linenumber, format, strerror(errno));
d_log(DLOG_GENERAL, WHERE, buff, arg);
}
/*
** Return printable representation of char.
*/
static char *
seechar(c)
char c;
{
static char buff[8];
if (isprint(c) || c == '\n') {
buff[0] = c;
buff[1] = '\0';
}
else
(void)sprintf(buff, "\\%03o", c);
return buff;
}
/*
** Parse a C-style escape string. Returns the length of the string
** or -1 on error.
*/
static int
canonstring(in, out)
register char *in;
register char *out;
{
int count;
char c;
char *save;
for (save = out; c = *in++; )
if (c != '\\')
*out++ = c;
else {
switch (c = *in++) {
default: *out++ = c; break;
case 'b': *out++ = '\b'; break;
case 'n': *out++ = '\n'; break;
case 'r': *out++ = '\r'; break;
case 't': *out++ = '\t'; break;
case 'x': *out++ = SC_SLEEP; break;
case '#': *out++ = SC_BREAK; break;
case '\0':
return -1;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
for (c -= '0', count = 1; count < 3; count++, in++) {
if (*in < '0' || *in > '7')
break;
c = (c << 3) | (*in - '0');
}
*out++ = c;
}
}
*out = '\0';
return out - save;
}
/*
** Snip the next work out of the buffer.
*/
static int
getword(p, start, next)
register char *p;
char **start;
char **next;
{
/* Skip leading whitespace, see if there's anything left. */
while (*p == ' ' || *p == '\t')
p++;
if (*p == '\0')
return ERR_NOWORD;
if (*p == '"') {
for (*start = ++p; *p != '"'; p++)
if (*p == '\0')
return ERR_QUOTE;
*p++ = '\0';
}
else {
for (*start = p; *p && *p != ' ' && *p != '\t'; )
p++;
if (*p)
*p++ = '\0';
}
*next = p;
return D_OK;
}
#define END (&temp[MAXLINE - 2])
/*
** Handle $-expansion
*/
static int
expanddollars(buff)
char *buff;
{
char temp[MAXLINE];
char *out;
char *p;
int i;
for (out = temp, p = buff; *p; p++)
if (*p != '$') {
if (out >= END)
return ERR_TOOLONG;
*out++ = *p;
}
else if (*++p == '$') {
if (out >= END)
return ERR_TOOLONG;
*out++ = *p;
}
else {
if (!isdigit(*p) || (i = *p - '0') > use_nfields)
return ERR_BADVAR;
if (out + strlen(use_fields[i]) >= END)
return ERR_TOOLONG;
out += strlen(strcpy(out, use_fields[i]));
}
*out++ = '\0';
(void)strcpy(buff, temp);
return D_OK;
}
/*
** Parse a line and break it into fields. Return the number of fields
** or an error.
*/
static int
getfields(buff, maxfields, fields)
char *buff;
int maxfields;
char *fields[];
{
char *p;
char *next;
int i;
if (strchr(buff, '$') != NULL && (i = expanddollars(buff)) != D_OK)
return i;
for (i = 0, p = buff; i < maxfields; i++, p = next)
switch (getword(p, &fields[i], &next)) {
case ERR_NOWORD:
return i;
case ERR_QUOTE:
return ERR_QUOTE;
}
return ERR_FIELDS;
}
/*
** Get a line of text and parse it into a command and fields. Return
** the command number or an error.
*/
static int
getcommand(buff, nfields, fields)
char *buff;
int *nfields;
char *fields[];
{
char *p;
register COMMAND *cp;
do {
linenumber++;
if (fgets(buff, MAXLINE, scriptfile) == NULL)
return S_EOF;
if ((p = strchr(buff, '\n')) == NULL) {
syntax_error("Line too long");
return D_FATAL;
}
*p = '\0';
} while (buff[0] == '\0' || buff[0] == '#');
/* Get the first word. */
switch (getword(buff, &fields[0], &p)) {
default:
return D_FATAL;
case ERR_QUOTE:
syntax_error("Missing end quote");
return D_FATAL;
case D_OK:
break;
}
/* Find the command word. */
for (cp = commands; cp->Name; cp++)
if (strcmp(fields[0], cp->Name) == 0)
break;
if (cp->Name == NULL) {
syntax_error("Bad command");
return D_FATAL;
}
/* Parse the rest of the line. */
*nfields = getfields(p, MAXFIELDS - 1, &fields[1]);
if (*nfields < 0) {
switch (*nfields) {
default:
syntax_error("Internal error in parser");
break;
case ERR_FIELDS:
syntax_error("Too many fields");
break;
case ERR_QUOTE:
syntax_error("Missing end quote");
break;
case ERR_BADVAR:
syntax_error("Bad \"$\" variable");
break;
case ERR_TOOLONG:
syntax_error("Line too long");
break;
}
return D_FATAL;
}
/* Make sure the right number of fields are present. */
if (*nfields >= cp->MinFields && *nfields <= cp->MaxFields)
return cp->Code;
syntax_error("Wrong number of fields");
return D_FATAL;
}
/*
** Command dispatcher.
*/
static int
dispatch(c, nfields, fields)
int c;
int nfields;
char *fields[];
{
int i;
switch (c) {
default:
syntax_error("Unknown command in dispatch");
return D_FATAL;
case S_MARK:
do_mark();
break;
case S_REPLAY:
do_replay();
break;
case S_LOG:
d_log(DLOG_GENERAL, WHERE, "%s", fields[1]);
break;
case S_COMMENT:
break;
case S_ABORT:
return D_FATAL;
case S_GO:
return D_OK;
case S_ALT:
case S_SELST:
case S_SELEND:
return c;
case S_XMIT:
return (i = do_xmit(fields[1])) < 0 ? i : D_CONTIN;
case S_RECV:
return (i = do_recv(fields[1], fields[2])) < 0 ? i : D_CONTIN;
case S_USEFILE:
return do_use(fields[1], nfields, fields) < 0 ? D_FATAL : D_CONTIN;
case S_EOF:
/* Unexpected EOF. Revert to previous script if possible. */
return closescript() > 0 ? D_CONTIN : D_FATAL;
}
return D_CONTIN;
}
/*
** Execute all commands in a block.
*/
static int
do_block()
{
char buff[MAXLINE + 2];
char *fields[MAXFIELDS];
int nfields;
int c;
int i;
for ( ; ; ) {
if ((c = getcommand(buff, &nfields, fields)) < 0)
return c;
if ((i = dispatch(c, nfields, fields)) != D_CONTIN)
return i;
}
}
/*
** Skip forward until we hit the end of the current select block.
*/
static int
findblockend()
{
char buff[MAXLINE + 2];
char *fields[MAXFIELDS];
int nfields;
int i;
for ( ; ; ) {
if ((i = getcommand(buff, &nfields, fields)) < 0)
return i;
switch (i) {
case S_SELEND:
return S_SELEND;
case S_SELST:
if ((i = findblockend()) < 0)
return i;
break;
}
}
}
/*
** Find next alternative in current select block.
*/
static int
findnextalt()
{
char buff[MAXLINE + 2];
char *fields[MAXFIELDS];
int nfields;
int i;
for ( ; ; ) {
if ((i = getcommand(buff, &nfields, fields)) < 0)
return i;
switch (i) {
case S_SELEND:
case S_ALT:
return i;
case S_SELST:
if ((findblockend()) < 0)
return i;
break;
}
}
}
static int
runloop()
{
char buff[MAXLINE + 2];
char *fields[MAXFIELDS];
int nfields;
int nselect;
int i;
/* Go for it. */
for (nselect = 0; ; )
switch (i = do_block()) {
default:
if (i < 0)
return i;
syntax_error("Internal error in runloop");
return -1;
case D_OK:
return 0;
case D_FATAL:
return -1;
case D_NONFATAL:
/* Possibly-recoverable error. If in a select block, try the
* next alternate. */
if (nselect <= 0 || (i = findnextalt()) < 0)
return -1;
switch (i) {
default:
syntax_error("Bad syntax in script");
return -1;
case S_ALT:
continue;
case S_SELEND:
return -1;
}
case S_SELST:
/* The next line had better be an alternate */
if (getcommand(buff, &nfields, fields) != S_ALT) {
syntax_error("Missing \"alternate\" after \"{\"");
return -1;
}
nselect++;
continue;
case S_SELEND:
/* verify that there was a select block being looked at. If so,
* then the last alternate was the successful one. Continue
* normally. */
if (nselect-- <= 0) {
syntax_error("Bad \"}\"");
return -1;
}
continue;
case S_ALT:
/* First, make sure the context was correct */
if (nselect <= 0) {
syntax_error("Bad alternate");
return -1;
}
/* If we get here, then an alternate within a select was
* completed successfully. Move on. */
if ((i = findblockend()) < 0)
return -1;
nselect--;
switch (i) {
default:
syntax_error("Syntax error in alternate");
return -1;
case S_SELEND:
continue;
}
}
}
/*
** Read lines from the script, parse them, do the actions.
*/
int
runscript(rp, fp, tname)
REMOTE *rp;
FILE *fp;
char *tname;
{
int i;
char *fields[MAXFIELDS];
/* Set up globals. */
portfile = fp;
if (tname == NULL)
tranfile = NULL;
else {
tranfile = *tname == '+' ? fopen(++tname, "a") : fopen(tname, "w");
if (tranfile == NULL) {
misc_error("Can't create transcript \"%s\"", tname);
return -1;
}
}
transtyle = rp->Transtyle;
/* Set up the fields; they were parsed as zero-origin, but are
* referenced as one-origin. */
for (use_nfields = rp->FieldCount, i = 0; i < use_nfields; i++)
fields[i + 1] = use_fields[i + 1] = &rp->FieldData[rp->Fields[i]];
i = do_use(rp->Script, use_nfields, fields) < 0 ? -1 : runloop();
if (tranfile)
(void)fclose(tranfile);
return i;
}
/*
** Wait for a given string, with optional timeout.
*/
static void
scripttimeout()
{
longjmp(timerest, 1);
}
/*
** Write to the port, with a timeout.
*/
static int
writechar(p)
char *p;
{
int i;
if (tranfile && transtyle != TS_LOW) {
if (isprint(*p))
(void)fprintf(tranfile, "\tPUT %c (0%03o)\n", *p, *p);
else
(void)fprintf(tranfile, "\tPUT 0x%02x (0%03o)\n", *p, *p);
(void)fflush(tranfile);
}
(void)signal(SIGALRM, scripttimeout);
if (setjmp(timerest) == 0) {
(void)alarm(XMITWAIT);
i = write(fileno(portfile), p, 1);
(void)alarm(0);
/* Process the return codes. */
if (i == 1)
return D_OK;
if (i >= 0)
return D_NONFATAL;
if (errno != EINTR)
return D_FATAL;
}
io_error("Write time-out");
return D_INTRPT;
}
/*
** Transmit a string.
*/
do_xmit(string)
char *string;
{
register int i;
register char *p;
char buff[MAXLINE];
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "XMIT %s\n", string);
(void)fflush(tranfile);
}
/* Make canonical. */
if ((i = canonstring(string, buff)) < 0) {
syntax_error("Bad transmit string format");
return D_FATAL;
}
for (p = buff; --i >= 0; p++) {
if ((unsigned char)*p == (unsigned char)SC_SLEEP) {
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "\tSLEEP\n");
(void)fflush(tranfile);
}
(void)sleep(1);
}
else if ((unsigned char)*p == (unsigned char)SC_BREAK) {
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "\tBREAK\n");
(void)fflush(tranfile);
}
(void)ioctl(fileno(portfile), TIOCSBRK, (caddr_t)0);
(void)sleep(1);
(void)ioctl(fileno(portfile), TIOCCBRK, (caddr_t)0);
}
else if (writechar(p) < 0)
return D_FATAL;
}
return D_OK;
}
do_recv(p, timestr)
char *p;
char *timestr;
{
int length;
int i;
int timeout;
char buff[MAXLINE];
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "RECV %s %s\n", p, timestr);
(void)fflush(tranfile);
}
/* Convert the string. */
if ((length = canonstring(p, buff)) < 0) {
syntax_error("Bad receive string format");
return D_FATAL;
}
if (length > MATCHLEN) {
syntax_error("Receive string too long");
return D_FATAL;
}
/* Set up the timer */
if ((timeout = atoi(timestr)) < 1) {
syntax_error("Bad timeout value");
return D_FATAL;
}
(void)signal(SIGALRM, scripttimeout);
if (setjmp(timerest) == 0) {
/* Do the matching. */
(void)alarm((unsigned int)timeout);
while ((i = matchtext(buff)) == D_NOMATCH)
(void)getnextchar();
(void)alarm(0);
switch (i) {
default:
return D_FATAL;
case D_OK:
return D_OK;
case D_NONFATAL:
io_error("EOF while trying match");
return D_NONFATAL;
case D_FATAL:
io_error("Read error while trying match");
return D_FATAL;
case D_INTRPT:
break;
}
}
d_log(DLOG_INFO, WHERE, "No match for \"%s\" after %d seconds",
p, timeout);
return D_NONFATAL;
}
#define pushbackchar(c) (*pushptr++ = (c))
/*
** Check for a match between the string and the input stream.
*/
int
matchtext(p)
char *p;
{
int c;
int i;
if (*p == '\0')
return D_OK;
if ((c = getnextchar()) < D_OK)
return c;
if (c == *p) {
i = matchtext(++p);
if (i < D_OK)
pushbackchar(c);
return i;
}
pushbackchar(c);
return D_NOMATCH;
}
/*
** Read characters from the port in raw mode, or the pushback buffer.
*/
int
getnextchar()
{
int i;
char c;
/* Use pushback if there is any. */
if (pushptr > pushbuffer) {
c = *--pushptr;
if (tranfile && transtyle != TS_LOW) {
if (isprint(c))
(void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c);
else
(void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c);
(void)fflush(tranfile);
}
return c;
}
while ((i = readchar()) != EOF) {
/* filter out some of the junk characters */
c = toascii(i);
if (tranfile && transtyle != TS_LOW) {
if (isprint(c))
(void)fprintf(tranfile, "\t\tGET %4c (0%03o)\n", c, c);
else
(void)fprintf(tranfile, "\t\tGET 0x%02x (0%03o)\n", c, c);
(void)fflush(tranfile);
}
if (c != '\0' && c != '\177')
return c;
/* Ignore NUL and DEL because they are almost always just noise */
}
if (feof(portfile))
return D_NONFATAL;
if (errno == EINTR)
return D_INTRPT;
return D_FATAL;
}
static char *
copystring(p)
char *p;
{
char *new;
if ((new = malloc((unsigned int)strlen(p) + 1)) == NULL) {
misc_error("Malloc failed; can't copy \"%s\"", p);
return NULL;
}
return strcpy(new, p);
}
/*
** Open a script.
*/
int
do_use(sname, nfields, fields)
char *sname;
int nfields;
char *fields[];
{
register int i;
OPENFILE *op;
FILE *F;
if (nopenfiles > MAXFILES) {
syntax_error("Too many \"use\" commands");
return D_FATAL;
}
/* Get the full filename. */
if (*sname == '/')
(void)strcpy(scriptname, sname);
else
(void)sprintf(scriptname, "%s/%s", CONFIG_DIR, sname);
/* Open it. */
if ((F = fopen(scriptname, "r")) == NULL) {
misc_error("Can't open script file \"%s\"", scriptname);
return D_FATAL;
}
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "USING %s", scriptname);
for (i = 0; i < nfields; i++)
(void)fprintf(tranfile, " %s", fields[i + 1]);
(void)fprintf(tranfile, "\n");
(void)fflush(tranfile);
}
/* If this isn't the first file, save the current state. */
if (nopenfiles) {
if ((op = (OPENFILE *)malloc(sizeof (OPENFILE))) == NULL) {
misc_error("No space for openfile \"%s\"", scriptname);
return D_FATAL;
}
op->linenumber = linenumber;
op->scriptfile = scriptfile;
op->scriptname = copystring(scriptname);
for (op->nfields = use_nfields, i = 0; i < use_nfields; i++)
op->fields[i] = use_fields[i];
for (use_nfields = nfields - 1, i = 0; i < nfields; i++)
use_fields[i] = copystring(fields[i + 1]);
openfiles[nopenfiles - 1] = op;
}
nopenfiles++;
scriptfile = F;
linenumber = 0;
return D_OK;
}
int
closescript()
{
int i;
OPENFILE *op;
if (nopenfiles <= 0)
return 0;
if (scriptfile)
(void)fclose(scriptfile);
for (i = 0; i < use_nfields; i++)
free(use_fields[i]);
if (--nopenfiles > 0) {
op = openfiles[nopenfiles - 1];
scriptfile = op->scriptfile;
(void)strcpy(scriptname, op->scriptname);
free(op->scriptname);
linenumber = op->linenumber;
for (use_nfields = op->nfields, i = 0; i < use_nfields; i++)
use_fields[i] = op->fields[i];
free((char *)op);
return nopenfiles;
}
return 0;
}
/*
** Read characters from the TTY line and store them in a circular
** buffer so that they can be replayed if desired.
*/
do_replay()
{
replay = 1;
repcurr = repfirst;
repcount = repsize;
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "REPLAY\n");
(void)fflush(tranfile);
}
}
do_mark()
{
replay = 0;
repsize = 0;
repfirst = 0;
repnext = 0;
if (tranfile && transtyle != TS_LOW) {
(void)fprintf(tranfile, "SET MARK\n");
(void)fflush(tranfile);
}
}
static int
repskipline()
{
for (; repbuff[repfirst] != '\n' && repsize > 0; repsize--)
repfirst++;
if (repsize <= 0)
do_mark();
else {
repfirst++;
repsize--;
}
}
int
readchar()
{
register int c;
Top:
if (replay) {
/* Replaying input. Anything left? */
if (repcount == 0) {
replay = 0;
goto Top;
}
c = repbuff[repcurr];
if (++repcurr == REPBUFSIZE)
repcurr = 0;
repcount--;
/* Don't replay EOF's. They get in there when a timeout interrupts
* a read. */
if (c == EOF)
goto Top;
}
else {
if (repsize == REPBUFSIZE)
/* Ran out of buffer space. Rather then leave a partial line,
* delete the first line in the buffer. */
repskipline();
else
repsize++;
repbuff[repnext] = c = getc(portfile);
if (tranfile) {
if (transtyle == TS_LOW)
(void)fprintf(tranfile, "%s", seechar(c));
else if (isprint(c))
(void)fprintf(tranfile, "\tREAD %c (0%03o)\n", c, c);
else
(void)fprintf(tranfile, "\tREAD 0x%02x (0%03o)\n", c, c);
(void)fflush(tranfile);
}
if (++repnext == REPBUFSIZE)
repnext = 0;
}
return c;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.