628 lines
17 KiB
C
628 lines
17 KiB
C
/* Sample system-dependent communications i/o routines for embedded Kermit. */
|
|
|
|
/*
|
|
Author: Frank da Cruz.
|
|
Copyright (C) 1995, 2011.
|
|
Trustees of Columbia University in the City of New York.
|
|
All rights reserved.
|
|
See kermit.c for license.
|
|
*/
|
|
|
|
/*
|
|
The sample i/o routines for UNIX that provide packet i/o
|
|
functions on the console (login) device.
|
|
Copy this file, rename it appropriately, and replace the contents
|
|
of each routine appropriately for your platform.
|
|
|
|
Device i/o:
|
|
|
|
int devopen() Communications device - open
|
|
int pktmode() Communications device - enter/exit packet mode
|
|
int readpkt() Communications device - read a packet
|
|
int tx_data() Communications device - send data
|
|
int devclose() Communications device - close
|
|
int inchk() Communications device - check if bytes are ready to read
|
|
|
|
File i/o:
|
|
|
|
int openfile() File - open for input or output
|
|
ULONG fileinfo() Get input file modtime and size
|
|
int readfile() Input file - read data
|
|
int writefile() Output file - write data
|
|
int closefile() Input or output file - close
|
|
|
|
Full definitions below, prototypes in kermit.h.
|
|
|
|
These routines must handle speed setting, parity, flow control, file i/o,
|
|
and similar items without the kermit() routine knowing anything about it.
|
|
If parity is in effect, these routines must add it to outbound characters
|
|
and strip it from inbound characters.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#ifndef O_WRONLY
|
|
#include <sys/file.h>
|
|
#ifdef X_OK
|
|
#undef X_OK
|
|
#endif /* X_OK */
|
|
#endif /* O_WRONLY */
|
|
|
|
#include "cdefs.h"
|
|
#include "debug.h"
|
|
#include "platform.h"
|
|
#include "kermit.h"
|
|
|
|
UCHAR o_buf[OBUFLEN+8]; /* File output buffer */
|
|
UCHAR i_buf[IBUFLEN+8]; /* File output buffer */
|
|
|
|
/*
|
|
In this example, the output file is unbuffered to ensure that every
|
|
output byte is commited. The input file, however, is buffered for speed.
|
|
This is just one of many possible implmentation choices, invisible to the
|
|
Kermit protocol module.
|
|
*/
|
|
static int ttyfd, ofile = -1; /* File descriptors */
|
|
static FILE * ifile = (FILE *)0; /* and pointers */
|
|
|
|
/* Debugging */
|
|
|
|
#ifdef DEBUG
|
|
static FILE * dp = (FILE *)0; /* Debug log */
|
|
static int xdebug = 0; /* Debugging on/off */
|
|
|
|
void
|
|
dodebug(int fc, UCHAR * label, UCHAR * sval, long nval) {
|
|
|
|
if (fc != DB_OPN && !xdebug)
|
|
return;
|
|
if (!label)
|
|
label = "";
|
|
|
|
switch (fc) { /* Function code */
|
|
case DB_OPN: /* Open debug log */
|
|
if (dp) fclose(dp);
|
|
if (!*label) label = "debug.log";
|
|
dp = fopen(label,"w");
|
|
if (!dp) {
|
|
dp = stderr;
|
|
} else {
|
|
setbuf(dp,(char *)0);
|
|
}
|
|
xdebug = 1;
|
|
fprintf(dp,"DEBUG LOG OPEN\n");
|
|
return;
|
|
case DB_MSG: /* Write a message */
|
|
if (dp) fprintf(dp,"%s\n",label);
|
|
return;
|
|
case DB_CHR: /* Write label and character */
|
|
if (dp) fprintf(dp,"%s=[%c]\n",label,(char)nval);
|
|
return;
|
|
case DB_PKT: /* Log a packet */
|
|
/* (fill in later, fall thru for now...) */
|
|
case DB_LOG: /* Write label and string or number */
|
|
if (sval && dp)
|
|
fprintf(dp,"%s[%s]\n",label,sval);
|
|
else
|
|
fprintf(dp,"%s=%ld\n",label,nval);
|
|
return;
|
|
case DB_CLS: /* Close debug log */
|
|
if (dp) {
|
|
fclose(dp);
|
|
dp = (FILE *)0;
|
|
}
|
|
xdebug = 0;
|
|
}
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/* D E V O P E N -- Open communications device */
|
|
/*
|
|
|
|
Call with: string pointer to device name. This routine should get the
|
|
current device settings and save them so devclose() can restore them.
|
|
It should open the device. If the device is a serial port, devopen()
|
|
set the speed, stop bits, flow control, etc.
|
|
Returns: 0 on failure, 1 on success.
|
|
*/
|
|
int
|
|
devopen(char *device) {
|
|
ttyfd = 0;
|
|
return(1);
|
|
}
|
|
|
|
/* P K T M O D E -- Put communications device into or out of packet mode */
|
|
/*
|
|
Call with: 0 to put in normal (cooked) mode, 1 to put in packet (raw) mode.
|
|
For a "dumb i/o device" like an i/o port that does not have a login attached
|
|
to it, this routine can usually be a no-op.
|
|
Returns: 0 on failure, 1 on success.
|
|
*/
|
|
int
|
|
pktmode(short on) {
|
|
if (ttyfd < 0) /* Device must be open */
|
|
return(0);
|
|
system(on ? "stty raw -echo" : "stty sane"); /* Crude but effective */
|
|
return(1);
|
|
}
|
|
|
|
|
|
/* D E V S E T T I N G S */
|
|
|
|
int
|
|
devsettings(char * s) {
|
|
/* Get current device settings, save them for devrestore() */
|
|
/* Parse string s, do whatever it says, e.g. "9600;8N1" */
|
|
if (!pktmode(ON)) /* And put device in packet mode */
|
|
return(0);
|
|
return(1);
|
|
}
|
|
|
|
/* D E V R E S T O R E */
|
|
|
|
int
|
|
devrestore(void) {
|
|
/* Put device back as we found it */
|
|
pktmode(OFF);
|
|
return(1);
|
|
}
|
|
|
|
|
|
/* D E V C L O S E -- Closes the current open communications device */
|
|
/*
|
|
Call with: nothing
|
|
Closes the device and puts it back the way it was found by devopen().
|
|
Returns: 0 on failure, 1 on success.
|
|
*/
|
|
int
|
|
devclose(void) {
|
|
ttyfd = -1;
|
|
return(1);
|
|
}
|
|
|
|
/* I N C H K -- Check if input waiting */
|
|
|
|
/*
|
|
Check if input is waiting to be read, needed for sliding windows. This
|
|
sample version simply looks in the stdin buffer (which is not portable
|
|
even among different Unixes). If your platform does not provide a way to
|
|
look at the device input buffer without blocking and without actually
|
|
reading from it, make this routine return -1. On success, returns the
|
|
numbers of characters waiting to be read, i.e. that can be safely read
|
|
without blocking.
|
|
*/
|
|
int
|
|
inchk(struct k_data * k) {
|
|
#ifdef _IO_file_flags /* Linux */
|
|
if (ttyfd < 0) /* Device must be open */
|
|
return(0);
|
|
return((int) ((stdin->_IO_read_end) - (stdin->_IO_read_ptr)));
|
|
#else
|
|
#ifdef AIX /* AIX */
|
|
if (ttyfd < 0)
|
|
return(0);
|
|
return(stdin->_cnt);
|
|
#else
|
|
#ifdef SunOS /* Solaris and SunOS */
|
|
if (ttyfd < 0)
|
|
return(0);
|
|
return(stdin->_cnt);
|
|
#else
|
|
#ifdef HPUX /* HPUX */
|
|
if (ttyfd < 0)
|
|
return(0);
|
|
return(stdin->__cnt);
|
|
#else
|
|
return(-1);
|
|
#endif /* HPUX */
|
|
#endif /* SunOS */
|
|
#endif /* AIX */
|
|
#endif /* _IO_file_flags */
|
|
}
|
|
|
|
/* R E A D P K T -- Read a Kermit packet from the communications device */
|
|
/*
|
|
Call with:
|
|
k - Kermit struct pointer
|
|
p - pointer to read buffer
|
|
len - length of read buffer
|
|
|
|
When reading a packet, this function looks for start of Kermit packet
|
|
(k->r_soh), then reads everything between it and the end of the packet
|
|
(k->r_eom) into the indicated buffer. Returns the number of bytes read, or:
|
|
0 - timeout or other possibly correctable error;
|
|
-1 - fatal error, such as loss of connection, or no buffer to read into.
|
|
*/
|
|
|
|
int
|
|
readpkt(struct k_data * k, UCHAR *p, int len, int fc) {
|
|
int x, n, max;
|
|
short flag;
|
|
UCHAR c;
|
|
/*
|
|
Timeout not implemented in this sample.
|
|
It should not be needed. All non-embedded Kermits that are capable of
|
|
making connections are also capable of timing out, and only one Kermit
|
|
needs to time out. NOTE: This simple example waits for SOH and then
|
|
reads everything up to the negotiated packet terminator. A more robust
|
|
version might be driven by the value of the packet-length field.
|
|
*/
|
|
#ifdef DEBUG
|
|
char * p2;
|
|
#endif /* DEBUG */
|
|
|
|
#ifdef F_CTRLC
|
|
short ccn;
|
|
ccn = 0;
|
|
#endif /* F_CTRLC */
|
|
|
|
if (ttyfd < 0 || !p) { /* Device not open or no buffer */
|
|
debug(DB_MSG,"readpkt FAIL",0,0);
|
|
return(-1);
|
|
}
|
|
flag = n = 0; /* Init local variables */
|
|
|
|
#ifdef DEBUG
|
|
p2 = p;
|
|
#endif /* DEBUG */
|
|
|
|
while (1) {
|
|
x = getchar(); /* Replace this with real i/o */
|
|
c = (k->parity) ? x & 0x7f : x & 0xff; /* Strip parity */
|
|
|
|
#ifdef F_CTRLC
|
|
/* In remote mode only: three consecutive ^C's to quit */
|
|
if (k->remote && c == (UCHAR) 3) {
|
|
if (++ccn > 2) {
|
|
debug(DB_MSG,"readpkt ^C^C^C",0,0);
|
|
return(-1);
|
|
}
|
|
} else {
|
|
ccn = 0;
|
|
}
|
|
#endif /* F_CTRLC */
|
|
|
|
if (!flag && c != k->r_soh) /* No start of packet yet */
|
|
continue; /* so discard these bytes. */
|
|
if (c == k->r_soh) { /* Start of packet */
|
|
flag = 1; /* Remember */
|
|
continue; /* But discard. */
|
|
} else if (c == k->r_eom /* Packet terminator */
|
|
|| c == '\012' /* 1.3: For HyperTerminal */
|
|
) {
|
|
#ifdef DEBUG
|
|
*p = NUL; /* Terminate for printing */
|
|
debug(DB_PKT,"RPKT",p2,n);
|
|
#endif /* DEBUG */
|
|
return(n);
|
|
} else { /* Contents of packet */
|
|
if (n++ > k->r_maxlen) /* Check length */
|
|
return(0);
|
|
else
|
|
*p++ = x & 0xff;
|
|
}
|
|
}
|
|
debug(DB_MSG,"READPKT FAIL (end)",0,0);
|
|
return(-1);
|
|
}
|
|
|
|
/* T X _ D A T A -- Writes n bytes of data to communication device. */
|
|
/*
|
|
Call with:
|
|
k = pointer to Kermit struct.
|
|
p = pointer to data to transmit.
|
|
n = length.
|
|
Returns:
|
|
X_OK on success.
|
|
X_ERROR on failure to write - i/o error.
|
|
*/
|
|
int
|
|
tx_data(struct k_data * k, UCHAR *p, int n) {
|
|
int x;
|
|
int max;
|
|
|
|
max = 10; /* Loop breaker */
|
|
|
|
while (n > 0) { /* Keep trying till done */
|
|
x = write(ttyfd,p,n);
|
|
debug(DB_MSG,"tx_data write",0,x);
|
|
if (x < 0 || --max < 1) /* Errors are fatal */
|
|
return(X_ERROR);
|
|
n -= x;
|
|
p += x;
|
|
}
|
|
return(X_OK); /* Success */
|
|
}
|
|
|
|
/* O P E N F I L E -- Open output file */
|
|
/*
|
|
Call with:
|
|
Pointer to filename.
|
|
Size in bytes.
|
|
Creation date in format yyyymmdd hh:mm:ss, e.g. 19950208 14:00:00
|
|
Mode: 1 = read, 2 = create, 3 = append.
|
|
Returns:
|
|
X_OK on success.
|
|
X_ERROR on failure, including rejection based on name, size, or date.
|
|
*/
|
|
int
|
|
openfile(struct k_data * k, UCHAR * s, int mode) {
|
|
|
|
switch (mode) {
|
|
case 1: /* Read */
|
|
if (!(ifile = fopen(s,"r"))) {
|
|
debug(DB_LOG,"openfile read error",s,0);
|
|
return(X_ERROR);
|
|
}
|
|
k->s_first = 1; /* Set up for getkpt */
|
|
k->zinbuf[0] = '\0'; /* Initialize buffer */
|
|
k->zinptr = k->zinbuf; /* Set up buffer pointer */
|
|
k->zincnt = 0; /* and count */
|
|
debug(DB_LOG,"openfile read ok",s,0);
|
|
return(X_OK);
|
|
|
|
case 2: /* Write (create) */
|
|
ofile = creat(s,0644);
|
|
if (ofile < 0) {
|
|
debug(DB_LOG,"openfile write error",s,0);
|
|
return(X_ERROR);
|
|
}
|
|
debug(DB_LOG,"openfile write ok",s,0);
|
|
return(X_OK);
|
|
|
|
#ifdef COMMENT
|
|
case 3: /* Append (not used) */
|
|
ofile = open(s,O_WRONLY|O_APPEND);
|
|
if (ofile < 0) {
|
|
debug(DB_LOG,"openfile append error",s,0);
|
|
return(X_ERROR);
|
|
}
|
|
debug(DB_LOG,"openfile append ok",s,0);
|
|
return(X_OK);
|
|
#endif /* COMMENT */
|
|
|
|
default:
|
|
return(X_ERROR);
|
|
}
|
|
}
|
|
|
|
/* F I L E I N F O -- Get info about existing file */
|
|
/*
|
|
Call with:
|
|
Pointer to filename
|
|
Pointer to buffer for date-time string
|
|
Length of date-time string buffer (must be at least 18 bytes)
|
|
Pointer to int file type:
|
|
0: Prevailing type is text.
|
|
1: Prevailing type is binary.
|
|
Transfer mode (0 = auto, 1 = manual):
|
|
0: Figure out whether file is text or binary and return type.
|
|
1: (nonzero) Don't try to figure out file type.
|
|
Returns:
|
|
X_ERROR on failure.
|
|
0L or greater on success == file length.
|
|
Date-time string set to yyyymmdd hh:mm:ss modtime of file.
|
|
If date can't be determined, first byte of buffer is set to NUL.
|
|
Type set to 0 (text) or 1 (binary) if mode == 0.
|
|
*/
|
|
#ifdef F_SCAN
|
|
#define SCANBUF 1024
|
|
#define SCANSIZ 49152
|
|
#endif /* F_SCAN */
|
|
|
|
ULONG
|
|
fileinfo(struct k_data * k,
|
|
UCHAR * filename, UCHAR * buf, int buflen, short * type, short mode) {
|
|
struct stat statbuf;
|
|
struct tm * timestamp, * localtime();
|
|
|
|
#ifdef F_SCAN
|
|
FILE * fp; /* File scan pointer */
|
|
char inbuf[SCANBUF]; /* and buffer */
|
|
#endif /* F_SCAN */
|
|
|
|
if (!buf)
|
|
return(X_ERROR);
|
|
buf[0] = '\0';
|
|
if (buflen < 18)
|
|
return(X_ERROR);
|
|
if (stat(filename,&statbuf) < 0)
|
|
return(X_ERROR);
|
|
timestamp = localtime(&(statbuf.st_mtime));
|
|
sprintf(buf,"%04d%02d%02d %02d:%02d:%02d",
|
|
timestamp->tm_year + 1900,
|
|
timestamp->tm_mon + 1,
|
|
timestamp->tm_mday,
|
|
timestamp->tm_hour,
|
|
timestamp->tm_min,
|
|
timestamp->tm_sec
|
|
);
|
|
#ifdef F_SCAN
|
|
/*
|
|
Here we determine if the file is text or binary if the transfer mode is
|
|
not forced. This is an extremely crude sample, which diagnoses any file
|
|
that contains a control character other than HT, LF, FF, or CR as binary.
|
|
A more thorough content analysis can be done that accounts for various
|
|
character sets as well as various forms of Unicode (UTF-8, UTF-16, etc).
|
|
Or the diagnosis could be based wholly or in part on the filename.
|
|
etc etc. Or the implementation could skip this entirely by not defining
|
|
F_SCAN and/or by always calling this routine with type set to -1.
|
|
*/
|
|
if (!mode) { /* File type determination requested */
|
|
int isbinary = 1;
|
|
fp = fopen(filename,"r"); /* Open the file for scanning */
|
|
if (fp) {
|
|
int n = 0, count = 0;
|
|
char c, * p;
|
|
|
|
debug(DB_LOG,"fileinfo scan ",filename,0);
|
|
|
|
isbinary = 0;
|
|
while (count < SCANSIZ && !isbinary) { /* Scan this much */
|
|
n = fread(inbuf,1,SCANBUF,fp);
|
|
if (n == EOF || n == 0)
|
|
break;
|
|
count += n;
|
|
p = inbuf;
|
|
while (n--) {
|
|
c = *p++;
|
|
if (c < 32 || c == 127) {
|
|
if (c != 9 && /* Tab */
|
|
c != 10 && /* LF */
|
|
c != 12 && /* FF */
|
|
c != 13) { /* CR */
|
|
isbinary = 1;
|
|
debug(DB_MSG,"fileinfo BINARY",0,0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
*type = isbinary;
|
|
}
|
|
}
|
|
#endif /* F_SCAN */
|
|
|
|
return((ULONG)(statbuf.st_size));
|
|
}
|
|
|
|
|
|
/* R E A D F I L E -- Read data from a file */
|
|
|
|
int
|
|
readfile(struct k_data * k) {
|
|
if (!k->zinptr) {
|
|
#ifdef DEBUG
|
|
fprintf(dp,"readfile ZINPTR NOT SET\n");
|
|
#endif /* DEBUG */
|
|
return(X_ERROR);
|
|
}
|
|
if (k->zincnt < 1) { /* Nothing in buffer - must refill */
|
|
if (k->binary) { /* Binary - just read raw buffers */
|
|
k->dummy = 0;
|
|
k->zincnt = fread(k->zinbuf, 1, k->zinlen, ifile);
|
|
debug(DB_LOG,"readfile binary ok zincnt",0,k->zincnt);
|
|
|
|
} else { /* Text mode needs LF/CRLF handling */
|
|
int c; /* Current character */
|
|
for (k->zincnt = 0; (k->zincnt < (k->zinlen - 2)); (k->zincnt)++) {
|
|
if ((c = getc(ifile)) == EOF)
|
|
break;
|
|
if (c == '\n') /* Have newline? */
|
|
k->zinbuf[(k->zincnt)++] = '\r'; /* Insert CR */
|
|
k->zinbuf[k->zincnt] = c;
|
|
}
|
|
#ifdef DEBUG
|
|
k->zinbuf[k->zincnt] = '\0';
|
|
debug(DB_LOG,"readfile text ok zincnt",0,k->zincnt);
|
|
#endif /* DEBUG */
|
|
}
|
|
k->zinbuf[k->zincnt] = '\0'; /* Terminate. */
|
|
if (k->zincnt == 0) /* Check for EOF */
|
|
return(-1);
|
|
k->zinptr = k->zinbuf; /* Not EOF - reset pointer */
|
|
}
|
|
(k->zincnt)--; /* Return first byte. */
|
|
|
|
debug(DB_LOG,"readfile exit zincnt",0,k->zincnt);
|
|
debug(DB_LOG,"readfile exit zinptr",0,k->zinptr);
|
|
return(*(k->zinptr)++ & 0xff);
|
|
}
|
|
|
|
|
|
/* W R I T E F I L E -- Write data to file */
|
|
/*
|
|
Call with:
|
|
Kermit struct
|
|
String pointer
|
|
Length
|
|
Returns:
|
|
X_OK on success
|
|
X_ERROR on failure, such as i/o error, space used up, etc
|
|
*/
|
|
int
|
|
writefile(struct k_data * k, UCHAR * s, int n) {
|
|
int rc;
|
|
rc = X_OK;
|
|
|
|
debug(DB_LOG,"writefile binary",0,k->binary);
|
|
|
|
if (k->binary) { /* Binary mode, just write it */
|
|
if (write(ofile,s,n) != n)
|
|
rc = X_ERROR;
|
|
} else { /* Text mode, skip CRs */
|
|
UCHAR * p, * q;
|
|
int i;
|
|
q = s;
|
|
|
|
while (1) {
|
|
for (p = q, i = 0; ((*p) && (*p != (UCHAR)13)); p++, i++) ;
|
|
if (i > 0)
|
|
if (write(ofile,q,i) != i)
|
|
rc = X_ERROR;
|
|
if (!*p) break;
|
|
q = p+1;
|
|
}
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
/* C L O S E F I L E -- Close output file */
|
|
/*
|
|
Mode = 1 for input file, mode = 2 or 3 for output file.
|
|
|
|
For output files, the character c is the character (if any) from the Z
|
|
packet data field. If it is D, it means the file transfer was canceled
|
|
in midstream by the sender, and the file is therefore incomplete. This
|
|
routine should check for that and decide what to do. It should be
|
|
harmless to call this routine for a file that that is not open.
|
|
*/
|
|
int
|
|
closefile(struct k_data * k, UCHAR c, int mode) {
|
|
int rc = X_OK; /* Return code */
|
|
|
|
switch (mode) {
|
|
case 1: /* Closing input file */
|
|
if (!ifile) /* If not not open */
|
|
break; /* do nothing but succeed */
|
|
debug(DB_LOG,"closefile (input)",k->filename,0);
|
|
if (fclose(ifile) < 0)
|
|
rc = X_ERROR;
|
|
break;
|
|
case 2: /* Closing output file */
|
|
case 3:
|
|
if (ofile < 0) /* If not open */
|
|
break; /* do nothing but succeed */
|
|
debug(DB_LOG,"closefile (output) name",k->filename,0);
|
|
debug(DB_LOG,"closefile (output) keep",0,k->ikeep);
|
|
if (close(ofile) < 0) { /* Try to close */
|
|
rc = X_ERROR;
|
|
} else if ((k->ikeep == 0) && /* Don't keep incomplete files */
|
|
(c == 'D')) { /* This file was incomplete */
|
|
if (k->filename) {
|
|
debug(DB_LOG,"deleting incomplete",k->filename,0);
|
|
unlink(k->filename); /* Delete it. */
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
rc = X_ERROR;
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
int xerror() {
|
|
unsigned int x;
|
|
extern int errorrate; /* Fix this - NO EXTERNS */
|
|
if (!errorrate)
|
|
return(0);
|
|
x = rand() % 100; /* Fix this - NO C LIBRARY */
|
|
debug(DB_LOG,"RANDOM",0,x);
|
|
debug(DB_LOG,"ERROR",0,(x < errorrate));
|
|
return(x < errorrate);
|
|
}
|
|
#endif /* DEBUG */
|