/* bmodem.c - the BIX modem program */
/*
	by David Betz, BYTE Magazine/BIX
*/

#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <stat.h>
#include "dlink.h"

/* useful definitions */
#define TRUE	1
#define FALSE	0

/* transfer direction */
#define NONE	0
#define SND	1
#define RCV	2

/* global variables */
char mode[10];		/* transfer modes */
int batch;		/* batch mode flag */
int crc;		/* crc mode flag */
int big;		/* big block mode flag */
long baud = 9600L;	/* baud rate */
FILE *fp;		/* open file pointer */
long fsize;		/* size of current file */
jmp_buf xfr_error;	/* transfer error escape */

/* external variables */
extern int blknum;	/* current block number */

/* forward declarations */
long filesize();

/* main - the main routine */
main(argc,argv)
  int argc; char *argv[];
{
    long atol();
    int dir,src,dst,cnt,sts;
    char *p;

    /* setup the defaults */
    batch = FALSE;
    crc = FALSE;
    big = FALSE;
    dir = NONE;
    
    /* process the arguments */
    for (src = dst = 1, cnt = 0; src < argc; ++src) {

	/* handle options */
	if (argv[src][0] == '-') {
	    for (p = &argv[src][1]; *p != '\0'; )
	    	switch (*p++) {
		case 'b':		/* baud rate */
		    if (*p == '\0') {
			if (++src >= argc)
			    usage();
		    	baud = atol(argv[src]);
		    }
		    else {
			baud = atol(p);
		 	p = "";
		    }
		    break;
		case 'c':		/* crc */
	    	    crc = TRUE;
	    	    break;
		case 'k':		/* 1K blocks */
		    big = TRUE;
		    crc = TRUE;
	    	    break;
		case 'r':		/* receive */
		    dir = RCV;
		    break;
		case 's':		/* send */
		    dir = SND;
		    break;	
		case 'y':		/* batch mode */
		    batch = TRUE;
		    big = TRUE;
		    crc = TRUE;
	    	    break;
		default:
	    	    usage();
		}
	}

	/* handle a filename */
	else {
	    argv[dst++] = argv[src];
	    ++cnt;
	}
    }

    /* setup the mode string (for log messages) */
    mode[0] = '\0';
    if (batch) strcat(mode,"y");
    if (big) strcat(mode,"k");
    if (crc) strcat(mode,"c");

    /* do the transfer(s) */
    switch (dir) {
    case SND:	/* send file(s) */
	if (batch)
	    sts = snd_batch(cnt,&argv[1]);
	else {
	    if (cnt == 0)
		error("no file specified");
	    else if (cnt > 1)
		error("more than one file specified");
	    sts = snd_single(argv[1]);
	}
	break;
    case RCV:	/* receive file(s) */
	if (batch) {
	    if (cnt != 0)
		error("no filenames are allowed for batch receive");
	    sts = rcv_batch();
	}
	else {
	    if (cnt == 0)
		error("no file specified");
	    else if (cnt > 1)
		error("more than one file specified");
	    sts = rcv_single(argv[1]);
	}
	break;
    default:
	usage();
    }

    /* exit with completion status */
    exit(sts);
}

/* rcv_single - receive a single file */
int rcv_single(fname)
  char *fname;
{
    long curtime;
    int sts;

    /* open the output file */
    if ((fp = fopen(fname,"w")) == NULL) {
	printf("Can't open for output: %s\n",fname);
	return (1);
    }
    log("receive, name=%s, mode=%s",fname,mode);
    fsize = -1L;

    /* initialize the communications line */
    md_init(baud);

    /* trap transfer errors */
    if (setjmp(xfr_error)) {
	printf("[ transfer aborted ]\n");
	xy_cancel();
	md_done();
	return (ER_CANCEL);
    }

    /* transfer the data */
    sts = rcvdata();
    md_done();

    /* close the file */
    fclose(fp);

    /* report errors */
    if (sts) {
	printf("[ transfer failed: %s ]\n",errstring[sts]);
	unlink(fname);
    }

    /* log the transfer */
    log("completion status=%s",errstring[sts]);

    /* return with completion status */
    return (sts);
}

/* rcv_batch - receive files in batch mode */
int rcv_batch()
{
    char name[LBLKLEN];
    int sts;

    /* initialize the communications line */
    md_init(baud);

    /* trap transfer errors */
    if (setjmp(xfr_error)) {
	printf("[ transfer aborted ]\n");
	xy_cancel();
	md_done();
	return (ER_CANCEL);
    }

    /* transfer each file */
    for (sts = ER_OK; sts == ER_OK; ) {
	if ((sts = xy_rcvhdr(crc,name,&fsize)) == ER_OK) {
	    log("receive, name=%s, bytes=%ld, mode=%s",name,fsize,mode);
	    if (openfile(name)) {
		xy_sndack();
		sts = rcvdata();
		fclose(fp);
	    }
	    else {
		xy_cancel();
		sts = ER_CANCEL;
	    }
	    log("completion status=%s",errstring[sts]);
	}
    }

    /* restore the communications line */
    md_done();

    /* report errors */
    if (sts != ER_DONE)
	printf("[ transfer failed: %s ]\n",errstring[sts]);

    /* return status */
    return (sts);
}

/* snd_single - send a single file */
int snd_single(fname)
  char *fname;
{
    int blklen,sts;
    long curtime;

    /* open the input file */
    if ((fp = fopen(fname,"r")) == NULL) {
	printf("Can't open for input: %s\n",fname);
	return (1);
    }

    /* show the file size */
    fsize = filesize(fname);
    blklen = (big ? LBLKLEN : SBLKLEN);
    printf("[ bytes: %ld, blocks: %ld, block size: %d ]\n",
	   fsize,(fsize + (long)blklen - 1L) / (long)blklen,blklen);
    log("send, name=%s, bytes=%ld, mode=%s",fname,fsize,mode);

    /* initialize the communications line */
    md_init(baud);

    /* trap transfer errors */
    if (setjmp(xfr_error)) {
	printf("[ transfer aborted ]\n");
	xy_cancel();
	md_done();
	return (ER_CANCEL);
    }

    /* transfer the data */
    sts = snddata(blklen);
    md_done();

    /* close the file */
    fclose(fp);

    /* report errors */
    if (sts)
	printf("[ transfer failed: %s ]\n",errstring[sts]);

    /* log the transfer */
    log("completion status=%s",errstring[sts]);

    /* return the completion status */
    return (sts);
}

/* snd_batch - send files in batch mode */
int snd_batch(argc,argv)
  int argc; char **argv;
{
    long fsize,curtime;
    int sts;

    /* initialize the communications line */
    md_init(baud);

    /* trap transfer errors */
    if (setjmp(xfr_error)) {
	printf("[ transfer aborted ]\n");
	xy_cancel();
	md_done();
	return (ER_CANCEL);
    }

    /* send each file */
    for (sts = ER_OK; sts == ER_OK && --argc >= 0; ++argv)
	if (fp = fopen(*argv,"r")) {
	    fsize = filesize(*argv);
	    log("send, name=%s, bytes=%ld, mode=%s",*argv,fsize,mode);
	    if ((sts = xy_sndhdr(*argv,fsize)) == ER_OK)
		sts = snddata(big ? LBLKLEN : SBLKLEN);
	    log("completion status=%s",errstring[sts]);
	    fclose(fp);
	}

    /* end the batch transfer */
    if (sts == ER_OK)
	sts = xy_sndend();

    /* restore the communications line */
    md_done();

    /* report errors */
    if (sts)
	printf("[ transfer failed: %s ]\n",errstring[sts]);

    /* return the completion status */
    return (sts);
}

/* openfile - open the next file for a batch receive */
static int openfile(name)
  char *name;
{
    char *src,*dst;

    /* strip the directory part of the filename */
    for (src = &name[strlen(name)-1]; src >= name; --src)
	if (*src == '/' || *src == '\\')
	    break;

    /* translate the filename to lowercase */
    for (++src, dst = name; *src != '\0'; ++src)
	*dst++ = (isupper(*src) ? tolower(*src) : *src);
    *dst = '\0';

    /* open the output file */
    if ((fp = fopen(name,"w")) == NULL)
	return (error("can't open for output: %s",name));

    /* return successfully */
    return (TRUE);
}

/* rcvdata - receive data using x/ymodem */
static int rcvdata()
{
    unsigned char buf[LBLKLEN],*bp;
    int bc,sts;

    /* transfer the data */
    if ((sts = xy_rinit(crc)) == ER_OK) {

	/* receive each data block */
	while ((sts = xy_rcvblk(buf,&bc)) == ER_OK) {
	    for (bp = buf; --bc >= 0; )
		if (fsize < 0L)
		    putc(*bp++,fp);
		else if (fsize > 0L) {
		    putc(*bp++,fp);
		    --fsize;
		}
	}
    }
    return (sts == ER_EOF ? ER_OK : sts);
}

/* snddata - send data using x/ymodem */
static int snddata(blklen)
  int blklen;	/* block length (either 128 or 1024) */
{
    unsigned char buf[LBLKLEN],*bp;
    int bc,ch,sts;
    long size;

    /* transfer the data */
    if ((sts = xy_sinit()) == ER_OK) {

	/* send each byte */
	for (bp = buf, bc = 0; (ch = getc(fp)) != EOF; ) {
	    *bp++ = ch;
	    if (++bc == blklen) {
		if ((sts = xy_sndblk(buf,blklen)) != ER_OK)
		    return (sts);
		bp = buf;
		bc = 0;
	    }
	}

	/* flush partial buffer */
	if (bc > 0) {
	    while (++bc <= blklen)
		*bp++ = '\0';
	    if ((sts = xy_sndblk(buf,blklen)) != ER_OK)
		return (sts);
	}

	/* send EOT and wait for ACK */
	sts = xy_sndeot();
    }
    return (sts);
}

/* usage - show how to use the program */
usage()
{
    fprintf(stderr,"usage: bmodem -{sr}[bcky] filename...\n");
    fprintf(stderr,"   s        - send\n");
    fprintf(stderr,"   r        - receive\n");
    fprintf(stderr,"   b <baud> - baud rate\n");
    fprintf(stderr,"   c        - crc\n");
    fprintf(stderr,"   k        - 1K blocks\n");
    fprintf(stderr,"   y        - ymodem batch\n");
    exit(1);
}

/* error - report an error and exit */
error(msg)
  char *msg;
{
    fprintf(stderr,"error: %s\n",msg);
    exit(1);
}

/* filesize - return the size of a file */
static long filesize(fname)
  char *fname;
{
    struct stat buf;
    return (stat(fname,&buf) == EOF ? 0L : buf.st_size);
}
