#ifndef lint static char rcsid[] = "$Xukc: z88link.c,v 1.11 91/06/08 14:11:32 rlh2 Rel $"; #endif /* !lint */ /* I give lots of software away and here is the standard copyright * notice I use .. */ /* * Copyright 1991 Richard Hesketh / rlh2@ukc.ac.uk * * Permission to use, copy, modify and distribute this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Richard Hesketh not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. Richard Hesketh makes no * representations about the suitability of this software for any purpose. * It is provided "as is" without express or implied warranty. * * Richard Hesketh DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL Richard Hesketh * BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. * * Author: Richard Hesketh / rlh2@ukc.ac.uk, */ /* * File transfer ([]X Import/Export) for the Cambridge Computer * Z88 Laptop. * * This is a very simple file transfer program for UNIX machines * via /dev/tty? (where ? is normally "a" or "b" on a SPARCstation for * example). It recognises when it is connected to a pipe or file and so * is not restricted to a /dev serial device. * * It uses the Z88's Import/Export protocol encoding for the actual * transfer of data and also converts between Newline representations. * * No conversion to or from PipeDream files is attempted .. you * need a separate script for that. Something like a lex or perl script * that converts the %blah% constructs to something should do (I don't * have one yet). * * Usage details are presented at the end of this file. * * WARNING: Whilst I have taken every care to trap errors, it is still possible * during a transfer to bung up the tty serial line on the UNIX * end. Typing Control-C may restore the serial line back into * a sane state, however it can also leave it unusable. If this * happens the only thing that cures it is a machine reboot. * However, at 2400 baud this should not happen to you! * I hope to up the rate to at least 9600 when I figure out the * the tty driver 8-). * * Richard Hesketh * Computing Lab., University of Kent at Canterbury, * Canterbury, Kent, CT2 7NF, United Kingdom. * Tel: +44 227 764000 ext 7620/7590 Fax: +44 227 762811 * Email: [Internet] rlh2@ukc.ac.uk * [JANET] rlh2@uk.ac.ukc */ #define VERSION 1.1 #include #include /* names for errno */ #ifndef clipper /* pure BSD 4.3 machines have open(2) mode defined in */ #include /* open(2) modes */ #endif /* clipper */ #include /* ioctls for terminal interface */ #include #include /* for timeout on select */ #include /* for access(2) modes */ #include #define CONTROL(x) ((x) & (~ 0x40)) /* converts 'A' to '^A' */ /* Z88 Import/Export protocol codes */ #define XON '\021' #define XOFF '\023' #define BIN "\033B" #define ESC '\033' #define ZFILENAME_START "\033N" #define ZFILENAME_END "\033F" #define ZFILE_END "\033E" #define ZBATCH_END "\033Z" #define IN_BAUD_RATE B2400 #define OUT_BAUD_RATE B2400 #define TRUE 1 #define FALSE 0 #define FILENAME_SIZE 300 static void close_connection(); static char is_pipe = FALSE; static int exit_status = 0; static int confd; /* fd of outgoing connection */ static struct sgttyb save_serial; /* leave it as we found it */ struct sgttyb rsmode = { IN_BAUD_RATE, OUT_BAUD_RATE, -1, -1, RAW | ODDP | EVENP | TANDEM }; static char port[10]; static char *prog_name = "z88link"; /* return sys_errlist[errno] if in range */ static char * reason() { extern char *sys_errlist[]; extern int errno, sys_nerr; return((errno > 0 && errno < sys_nerr) ? sys_errlist[errno] : "unknown reason"); } /* * recvc() * Receive a character from the serial port. Returns the character, * or -1 if no character ready. */ static int recvc() { char ch; int s; errno = 0; do { if ((s = read(confd, &ch, 1)) == 1) return (ch & 0xff); } while (s < 0); if (is_pipe) { /* special case for input coming from a data file or | */ close_connection(); } return (-1); } /* * rawsendc() * Send a character to the serial port. * Should only be called after open_connection(). */ static void rawsendc(c) int c; { char ch; ch = c & 0xff; write(confd, &ch, 1); } /* * rawsends() * Send a string to the serial port using rawsendc(). */ static void rawsends(s) char *s; { char *p; for (p = s; *p != '\0'; ++p) rawsendc(*p); } /* * hex() * Converts a 4-bit number into the appropriate hexdecimal representation */ static unsigned char hex(b) unsigned char b; { return (b > 9 ? ('A' + b - 10) : ('0' + b)); } /* * binsendc() * Sends a unprintable or 8-bit character using an ESC B prefix. */ static void binsendc(ch) int ch; { char out[5]; unsigned char c = (unsigned)(ch & 0xff); if (c == '\012') (void)sprintf(out, "%s0D", BIN); else (void)sprintf(out, "%s%c%c", BIN, hex((c & 0xf0) >> 4), hex(c & 0x0f)); rawsends(out); } /* * sendc() * Send a character to the serial port. * Should only be called after open_connection(). * Characters out of the printable 7-bit ascii range (0x20 to 0x7E) * are sent using binsendc(). */ static void sendc(c) int c; { char ch; ch = c & 0xFF; if (ch < 0x20 || ch > 0x7e) { /* send using the BIN protocol prefix */ binsendc(ch); return; } else rawsendc(ch); } /* * sends() * Send a string to the serial port using sendc(). */ static void sends(s) char *s; { char *p; for (p = s; *p != '\0'; ++p) sendc(*p); } /* * sendfilename() * Send a filename to the Z88 in its Import/Export protocol. */ static void sendfilename(name) char *name; { char filename[FILENAME_SIZE]; (void)sprintf(filename, "%s%s%s", ZFILENAME_START, name, ZFILENAME_END); rawsends(filename); } /* * sendEOF() * Send the EOF marker out .. sends out End-Of-Batch if "true" */ static void sendEOF(last) int last; { if (last) rawsends(ZBATCH_END); else rawsends(ZFILE_END); } static int blocking_read() { int ch; while ((ch = recvc()) == -1); return (ch); } static int readfile(fp, size) FILE *fp; int *size; { int end_of_batch = 1, eof = 0, ch, num_bytes_read = 0; char found_escape = 0, found_binary = 0; unsigned char bin1; *size = 0; while (!eof) { ch = blocking_read(); if (found_binary == 1) { found_binary = 2; bin1 = ch > '9' ? ch - 'A' + 10 : ch - '0'; continue; } else if (found_binary == 2) { /* got the second part of binary character * so convert it to a real ascii and output that. */ found_binary = 0; bin1 *= 16; bin1 += ch > '9' ? ch - 'A' + 10 : ch - '0'; ch = bin1; if (ch == 0x0D) /* convert into correct form of newlines */ ch = '\012'; } else if (ch == ESC) { found_escape = 1; continue; } else if (found_escape) { if (ch == 'Z' || ch == 'E') { end_of_batch = (ch == 'Z'); eof = 1; continue; } if (ch == 'B') { /* is this a binary number? */ found_escape = 0; found_binary = 1; continue; } } else if (ch == 13) /* convert into correct form of newlines */ ch = '\012'; else if (ch == XON || ch == XOFF) /* ignore these? */ continue; if (putc(ch, fp) < 0) { fprintf(stderr, "%s: failed to save file (%s)\n", prog_name, reason()); exit_status = 2; close_connection(); /* NOTREACHED */ } else { num_bytes_read++; if (num_bytes_read % 1024 == 0) { putc('.', stderr); fflush(stderr); } } } *size = num_bytes_read; return (end_of_batch); } /* * get_filename() * Look for a filename in the Z88 protocol format and return it or NULL */ static char * get_filename() { int ch, i, escape_found = 0; static char bigbuf[FILENAME_SIZE]; i = 1; /* find the start of the filename .. performing some * re-synchronization upon error. */ while (i) { ch = blocking_read(); if (ch == ESC) { /* this must be a batch control character? */ ch = blocking_read(); if (ch == 'Z') return (char *)1; if (ch != 'N') return NULL; i = 0; } } /* we have found ZFILENAME_START so read filename until * ZFILENAME_END. */ i = 0; while(1) { ch = blocking_read(); if (ch == ESC) escape_found = 1; else if (escape_found) { if (ch == 'F') { /* end of the filename */ bigbuf[i] = '\0'; break; } bigbuf[i++] = ESC; bigbuf[i++] = ch; } else { escape_found = 0; bigbuf[i++] = ch; } }; /* when we have got here we have read the ZFILENAME_START, the * whole filename and ZFILENAME_END. */ return (bigbuf); } static void sane_tty(fd) int fd; { struct sgttyb basic; struct tchars chars; struct ltchars local; int mode; mode = NTTYDISC; if (ioctl(fd, TIOCSETD, (char *) &mode) != 0) perror("failed to set line discipline"); basic.sg_ispeed = B9600; basic.sg_ospeed = B9600; basic.sg_erase = 0x7f; basic.sg_kill = CONTROL('U'); basic.sg_flags = CRMOD | ECHO; if (ioctl(fd, TIOCSETP, (char *) &basic) != 0) perror("failed to set basic terminal parameters"); chars.t_intrc = CONTROL('C'); chars.t_quitc = CONTROL('\\'); chars.t_startc = CONTROL('Q'); chars.t_stopc = CONTROL('S'); chars.t_eofc = CONTROL('D'); chars.t_brkc = -1; if (ioctl(fd, TIOCSETC, (char *) &chars) != 0) perror("failed to set terminal special characters"); mode = LCRTERA | LCRTKIL | LPASS8 | LDECCTQ; if (ioctl(fd, TIOCLSET, (char *) &mode) != 0) perror("failed to set terminal local mode word"); local.t_suspc = CONTROL('Z'); local.t_dsuspc = CONTROL('Y'); local.t_rprntc = CONTROL('R'); local.t_flushc = CONTROL('O'); local.t_werasc = CONTROL('W'); local.t_lnextc = CONTROL('V'); if (ioctl(fd, TIOCSLTC, (char *) &local) != 0) perror("failed to set terminal local characters"); } /* * close_connection() * Restores the state of the serial port and closes the file * associated with it. */ static void close_connection() { if (!is_pipe) { ioctl(confd, TIOCSETP, &save_serial); sane_tty(confd); (void)close(confd); } exit(exit_status); } /* * open_connection() * Opens the serial line and starts a connection to the given host. */ static int open_connection(port, receiving_files) char *port; char receiving_files; { struct sgttyb serp; int flags, attempts, ch, ttyfd; if (!isatty(confd = fileno(receiving_files ? stdin : stdout))) { /* Input is being redirected to/from a pipe or file. * So use stdin/stdout instead of the tty as the serial line. */ is_pipe = TRUE; return TRUE; } /* * Open the serial port, and set it to the required baud rate. * Save the parameters so we can reset it again when it's closed. */ if ((confd = open(port, O_RDWR)) == -1) { perror(port); return FALSE; } ioctl(confd, TIOCEXCL, 0); /* get exclusive use */ (void)signal(SIGINT, close_connection); (void)signal(SIGHUP, close_connection); (void)signal(SIGQUIT, close_connection); ioctl(confd, TIOCGETP, &save_serial); ioctl(confd, TIOCSETP, &rsmode); return TRUE; } static void usage() { fprintf(stderr, "usage: %s [a|b] [-r | [file1 ..]]\n", prog_name); fprintf(stderr, "\ta = /dev/ttya .. b = /dev/ttyb\n"); fprintf(stderr, "\t-r = receive files\n"); fprintf(stderr, "\tfile1 .. = files to send\n"); exit(1); } /******************************************************************/ static int make_directories(filename) char *filename; { extern char *index(); char *slash, name[FILENAME_SIZE]; int offset = 0, need_new_directories = 0; (void)strcpy(name, filename); while ((slash = index(name + offset, '/')) != NULL) { if (slash == name) { /* we have a filename starting from root! */ offset++; continue; } *slash = '\0'; offset = slash - name + 1; errno = 0; if (access(name, W_OK|X_OK) < 0 && errno == ENOENT) { /* last directory in current pathname does not exist */ *slash = '/'; need_new_directories = 1; break; } else if (errno > 0) { /* some other error occurred */ return (0); } *slash = '/'; } if (!need_new_directories) return (1); /* no directories to make */ /* we must now make all the directories from the "name + offset" * downwards .. only stopping on a permissions or file system full * error. */ while (*slash != '\0') { *slash = '\0'; printf("Making directory: %s", name); if (mkdir(name, 0755) < 0) { printf(" ... Failed! (%s)\n", reason()); return (0); } else printf("\n"); *slash = '/'; slash++; while (*slash != '\0' && *slash != '/') slash++; } return (1); } static void receive_files() { int end_of_batch = 0; char *filename; FILE *fp; int size; if (open_connection(port, TRUE) < 0) { fprintf(stderr, "%s: failed to open %s\n", prog_name, port); exit(2); } printf("Waiting for incoming files ...\n"); do { if ((filename = get_filename()) == NULL) { fprintf(stderr, "%s: garbled/missing filename in receive\n", prog_name); exit_status = 2; close_connection(); /* NOTREACHED */ } if (filename == (char *)1) /* End of batch found */ break; if (!make_directories(filename) || (fp = fopen(filename, "w")) == NULL) { fprintf(stderr, "%s: cannot upload file into %s (%s)\n", prog_name, filename, reason()); exit_status = 2; close_connection(); /* NOTREACHED */ } printf("Starting reception of %s (one dot equals 1k)\n", filename); size = 0; if ((end_of_batch = readfile(fp, &size)) < 0) { fprintf(stderr, "%s: failed to upload %s\n", prog_name, filename); exit_status = 2; close_connection(); /* NOTREACHED */ } if (fclose(fp) < 0) { fprintf(stderr, "%s : failed to close uploaded file %s (%s)\n", prog_name, filename, reason()); exit_status = 2; close_connection(); /* NOTREACHED */ } if (size >= 1024) putc('\n', stderr); fprintf(stderr, "End of successful reception of %s (%d bytes)\n", filename, size); } while (end_of_batch == 0); close_connection(); } static int send_file(fp, size) FILE *fp; int *size; { int ch; while ((ch = fgetc(fp)) != EOF) { (*size)++; sendc(ch); if (*size % 1024 == 0) { putc('.', stderr); fflush(stderr); } } return 0; } static void send_files(files, count) char **files; int count; { FILE *fp; int i, size; if (open_connection(port, FALSE) < 0) { fprintf(stderr, "%s: failed to open %s\n", prog_name, port); exit(2); } for (i = 0; i < count; i++) { if ((fp = fopen(files[i], "r")) == NULL) { fprintf(stderr, "%s: unable to send %s (%s)\n", prog_name, files[i], reason()); continue; } fprintf(stderr, "Starting transmission of %s (one dot equals 1k)\n", files[i]); sendfilename(files[i]); size = 0; if (send_file(fp, &size) < 0) { fprintf(stderr, "%s: transmission of %s somehow failed .. file may be corrupted\n", prog_name, files[i]); } if (size >= 1024) putc('\n', stderr); fprintf(stderr, "Finished transmission of %s (%d bytes)\n", files[i], size); sendEOF(i == (count - 1)); } close_connection(); } /* Main routine */ /* * This program is a sort of filter for an RS232 tty port on a UNIX box * connected to a Cambridge Z88 laptop. It is used for sending or receiving * files via the Z88's built-in Import/Export tool. * * Compile with .. * * cc -o z88link z88link.c * * (it has been built and tested on a SPARCstation 1 running SunOS 4.1.1 * and has also just been built (but not tested) on a DECstation 3100, * VAXstation 3200, Sun 3, Sun 386i and Orion Clipper [a what?]) * * Setup for Z88 .. * * go into the control panel ([]s) and set the following .. * * Transmit baud rate = 2400 * Receive baud rate = 2400 * Parity = None * Xon/Xoff = Yes * * Invoked with .. * * z88link [a|b] -r * Reads files from the Z88 until an END-OF-BATCH is received. * We are sent the name of the file etc and do any processing * necessary. * * z88link [a|b] file1 file2 ... * Sends the named files to the Z88 as a batch of files. * We send the filenames and do any conversion necessary. * * I use it for doing daily backups of all the files, in which case I use .. * * (on UNIX) % z88link a -r * * (on z88) invoke the Import/Export tool ([]x) * enter "s" to send a file * and enter ":RAM.?//*" to send all the files to UNIX * * z88link creates any missing directories when needed and is very chatty * about what it is doing 8-). * * z88link can also take advantage of pipes and file redirection. This * means that you are not stuck with having to use /dev/tty?, you can * use any communications link available. Reception and Transmission is * still done using the Import/Export protocol. For example if you have * a file "erik.bas" which is in the Import/Export protocol format you can * unpack on to a UNIX machine using file redirection .. * * % z88link a -r < erik.bas * * or using a pipe ... * * % cat erik.bas | z88link a -r * * Of course, you can also encode files in the Import/Export protocol as * well .. * * % z88link a erik.bas > IE_erik.bas * */ main(argc, argv) int argc; char *argv[]; { prog_name = argv[0]; if (argc < 3) usage(); (void)sprintf(port, "/dev/tty%c", *argv[1]); if (strcmp(argv[2], "-r") == 0) { if (argc != 3) usage(); receive_files(); } else { argv++; argv++; argc--; argc--; send_files(argv, argc); } return (0); }