/*
   This programm enables easy filetransfer between different computers
   using the standard V24 port.
   This programm must run as independend job on both computers.
   Commands are passed in a file on the RAM disk on one of both
   computers. The Default filename is RAM:SERQL.IN

   Commands:
   localfilename > remotefilename       send local file to host
   localfilename < remotefilename       transfer host file
   END                                  last command in file
   FINISH                               stop programm

   Machine dependend routines:
   short getser()                  reads one byte as integer from V24
   void  putser(i)                 write one integer as byte to V24
   void  initraps()                initialize serial port
   void  restoretraps()            restore system parameters
   long  fsize(str)                return length of a file
   void  delay(n)                  suspend job for n/50 seconds
*/

#include "stdio.h"

main()
{
   short       a,c,d,cflg;
   long        i,atoli();
   FILE        *fopen() , *fp;
   char        local[80],remote[80],s[80],*fgets();

   printf("File Transfer Programm               by Rainer Kowallik\n");
   printf("Commands are given in RAM:SERQL.IN\nCommands:\n");
   printf("localfilename > remotefilename       send local file to host\n");
   printf("localfilename < remotefilename       transfer host file\n");
   printf("END                                  last command in file\n");
   printf("FINISH                               stop programm\n");
   initraps(); cflg=1;
   while(cflg == 1) {
      fp=fopen("RAM:SERQL.IN","r");         /* try to open command file */
      if(fp != NULL) {
         while(1 == 1) {
            if(fgets(s,80,fp) == NULL) break;
            a=strlen(s)-1; if(s[a] == '\n') s[a]=0;
            printf("command: %s\n",s);
            if(lexcmp(s,"END") == 0) break;
            if(lexcmp(s,"FINISH") ==0) {cflg=-1; break; }
            a=instr(" ",s);
            midstr(local,s,0,a-1);           /* get AMIGA filename */
            rightstr(s,s,a+1); left(s); d=0;
            if(s[0] == '<') d=1;             /* get direction of transfer */
            if(s[0] == '>') d=-1;
            if(d == 0) {
               printf("SERQL: bad syntax in command line\n");
               break;
            }
            rightstr(remote,s,1); left(remote); /* get remote filename */
            /* transmit files */
            switch(d)
            {
            case 1 :       /* get remotefile from host computer */
               writestr(".Send"); writestr(remote);
               readfile(local);
               break;
            case -1 :      /* write local file to host computer */
               writestr(".Receive"); writestr(remote);
               writefile(local);
               break;
            }
         }
         fclose(fp);
         unlink("RAM:SERQL.IN");             /* remove command file */
      }
      c=getser();
      if(c == '.') {
         readstr(s); d=0;
         if(lexcmp(s,"Send") == 0) d=-1;
         if(lexcmp(s,"Receive") == 0) d=1;
         switch(d)
         {
         case 1 :       /* receive file from host */
            readstr(local); readfile(local);
            break;
         case -1 :      /* send file to host */
            readstr(local); writefile(local);
            break;
         case 0:        /* illegal command */
            printf("illegal command received from host:\n%s\n",s);
            break;
         }
      }
      delay( 2 ) ;
   }
   printf("Finish\n");
   restoretraps();
}


/* -----------------------------------
   write file to serial port
   ----------------------------------- */

writefile(fnam)
char  fnam[];
{  short c,i,n;
   long  fl,csum,fsize();
   char  s[80];
   FILE  *fp;

   fl=fsize(fnam);                           /* get length of file */
   if( fl < 0L) return();
   fp=fopen(fnam,"r");                       /* open file */
   if(fp == NULL) {
      printf("can not open input file: %s\n",fnam);
      return();
   }
   sendsync() ;                              /* try to synchronize */
   litoa(fl,s); writestr(s);                 /* send length of file */
   c=0; csum=0;                              /* initialize checksum */
   while(c != EOF) {                         /* now begin to send ... */
      c=fgetc(fp); if(c == EOF) break;       /* the file contents ... */
      putser(c); csum = csum + c; }          /* to the serial port */
   sendsync() ;                              /* try to synchronize */
   litoa(csum,s); writestr(s);               /* and send checksum */
   fclose(fp);
}


/* -----------------------------------
   read file from serial port
   ----------------------------------- */
readfile(fnam)
char  fnam[];
{  short  c,i,n;
   long   cnt,fl,csum,atoli();
   char   s[80];
   FILE   *fp;

   fp=fopen(fnam,"w");                       /* open file */
   if(fp == NULL) {
      printf("can not open outputfile: %s\n",fnam);
      return();
   }
   synchron();                               /* get synchronization */
   readstr(s); fl=atoli(s);                  /* get file length */
   csum=0;                                   /* initialize checksum */
   for(cnt=1; cnt <= fl; cnt++) {
      c=wtgetser(); csum = csum + c;         /* read text from V24 */
      putc(c,fp);                            /* and write to file */
   }
   fclose(fp);                               /* close and flush file */
   synchron(s);                               /* get synchronization */
   readstr(s); cnt=atoli(s);                 /* get checksum */
   if(csum != cnt) printf("Checksum error on file :  %s\n",fnam);
}


/* ------------------------------
   send synchronization sequence
   ------------------------------ */
sendsync()
{  short i ;

   writestr( "//////" ) ;
   writestr( "0123456789" ) ;
}


/* ---------------------------------------------
   read synchronization string from serial port
   --------------------------------------------- */
synchron()
{  short i ;
   char  s[80] ;

   while( 1 == 1 ) {
      readstr( s ) ;
      if( lexcmp( s, "0123456789" ) == 0 ) break ;
   }
}


/* -------------------------------------------
   return position of a substring in a string
   ------------------------------------------- */
instr(substr,str)
char  str[],substr[];
{  short i,p,flg,l1,l2;

   l1=strlen(str); l2=strlen(substr);
   for(p=0; p < l1; p++) {
      flg=0;
      for(i=0; i < l2; i++) {
         if(str[p+i] != substr[i]) {
            flg=-1; break;
         }
      }
      if(flg == 0) return(p);
   }
   return(-1);
}


/* -------------------------------------------------
   return a substring from within a mainstring
   ------------------------------------------------- */
midstr(substr,str,ss,es)
char  substr[],str[];
short ss,es;
{  short i,j;

   i=0;
   for(j=ss; j <= es; j++) substr[i++]=str[j];
   substr[i]=0;
}


/* ---------------------------------------------------
   return right hand end of a string
   --------------------------------------------------- */
rightstr(substr,str,ss)
char  substr[],str[];
short ss;
{  short l;

   l=strlen(str); midstr(substr,str,ss,l-1);
}


/* -----------------------------------------------------
   return left hand side of a string
   ----------------------------------------------------- */
leftstr(substr,str,es)
char  substr[],str[];
short es;
{
   midstr(substr,str,0,es);
}


/* -------------------------------------------------------
   adjust string to the left, removing spaces
   ------------------------------------------------------- */
left(str)
char  str[];
{  short i;

   i=0;
   while(str[i] == ' ') i++;
   rightstr(str,str,i);
}


/* -------------------------------------------------------
   reverse a string
   ------------------------------------------------------- */
reverse(str)
char  str[];
{  short l,n,i,c;

   l=strlen(str)-1, n=l/2;
   for(i=0; i <=n; i++) {
      c=str[l-i]; str[l-i]=str[i]; str[i]=c;
   }
}


/* ------------------------------------------------------
   compare two strings equating upper and lower case
   ------------------------------------------------------ */
lexcmp(str1,str2)
char  str1[],str2[];
{  char s1[256],s2[256];

   strcpy(s1,str1); strcpy(s2,str2);
   strupc(s1); strupc(s2);
   return(strcmp(s1,s2));
}


/* ---------------------------------------------
   convert string to upper case
   --------------------------------------------- */
strupc(str)
char  str[];
{  short  l,i;

   l=strlen(str);
   for(i=0; i < l; i++) str[i]=toupper(str[i]);
}


/* -------------------------------
   Convert Long integer to string
   ------------------------------- */
litoa(n,s)
char s[];
long n;
{  long i,sign;

   if ((sign = n) < 0)
      n = -n;                    /* take absolute value */
   i = 0;
   do {                          /* generate numbers from right side */
      s[i++] = n % 10 + '0';     /* next number */
   } while((n /= 10) > 0);       /* remove number */
   if (sign < 0)
      s[i++] = '-';
   s[i] = '\0';
   reverse(s);
}


/* ---------------------------------------------------
   convert string to long integer number
   --------------------------------------------------- */
long atoli(s)
char s[];
{  long i,n,sign;

   for(i=0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t'; i++)
      ;                       /* forget spaces */
   sign = 1L;
   if(s[i] == '+' || s[i] == '-')   /* sign ? */
      sign = (s[i++] == '+') ? 1 : -1;
   for (n = 0; s[i] >= '0' && s[i] <= '9'; i++)
      n = 10 * n + s[i] - '0';
   return(sign * n);
}


/* -----------------------------------------------------
   send string to serial port. send LF after string.
   ----------------------------------------------------- */
writestr(s)
char  s[];
{  int      i,l,c;

   l=strlen(s);
   for(i = 0 ; i<l ; i++) {
      c=s[i]; putser(c); }
   putser(10);
}


/* ----------------------------------------------------
   read string from serial port
   ---------------------------------------------------- */
readstr(s)
char  s[];
{  int      i,c;

   i=0; c=0;
   while(c != 10) {
      c=wtgetser(); s[i++]=c;
   }
   s[i-1]=0;
}


/* ----------------------------------------------------
   get filesize of file with given name.
   Returns negative value if the file does not exist
   W A R N I N G !!!! C O M P I L E R D E P E N D E N T
   ---------------------------------------------------- */
long fsize(fnam)
char     fnam[];
{  short    fd,open();
   long     l,lseek();

   fd=open(fnam,0);
   if(fd == -1)
      l=-1;
   else {
      l=lseek(fd,0L,2);
      close(fd); }
   return(l);
}


wtgetser()
{  int c;

   c=-1;
   while(c == -1) c=getser();
   return(c);
}
/* Set up Harware and Interruptvectors */

initraps()
{
#asm
OldOpenLibrary EQU -408
DOS_read EQU   -42
REGBASE  EQU   $DFF000
INTENA   EQU   REGBASE+$09A
INTREQ   EQU   REGBASE+$09C
INTREQR  EQU   REGBASE+$01E
SERPER   EQU   REGBASE+$032
SERDATR  EQU   REGBASE+$018
SERDAT   EQU   REGBASE+$030
HANDSHAKE EQU  $BFD000
B_4800   EQU   745
B_9600   EQU   372
SERQIN   EQU   256    ; length of input queue for serial data
STOPSER  EQU   SERQIN/2-8 ; maximum characters difference between read and write
;
; start initializing traps
;
   LEA      OLDVECTS,A0
   MOVE.L   $80,(A0) ; save TRAP #0 vector
   LEA      SUPER_ON,A1
   MOVE.L   A1,$80   ; set up Supervisormode routine for TRAP #0
;
   TRAP     #0        ; enter supervisor mode
   ORI      #$700,SR  ; disable interrupts
   MOVE.L   $74,4(A0) ; Save Autointerrupt vector level 5 (Read Buffer Full)
   LEA      READSER,A1
   MOVE.L   A1,$74   ; set up routine to read the serial port on interrupt 5
;
   MOVE.W   #B_9600,D0
   BSET     #15,D0   ; 9 bit receive data
   MOVE.W   D0,SERPER ; Set baudrate
;
   CLR.W    D0
   BSET     #15,D0   ; SET operation to INTENA
   BSET     #14,D0   ; enable interrupts
   BSET     #11,D0   ; RBF interrupt enable
   MOVE.W   D0,INTENA
   ANDI     #0,SR    ; enable interrupts to processor
#endasm
}

/* -------------------------------------------------------------
                 Restore old exception vectors
   ------------------------------------------------------------- */
restoretraps()
{
#asm
   LEA      OLDVECTS,A0
   MOVE.L   (A0),$80    ; restore TRAP #0
   MOVE.L   4(A0),$74   ; restore Level 5 autovector
#endasm
}

/* ----------------------------------------------------------------
               read data from the serial input queue
   ---------------------------------------------------------------- */
getser()
{
#asm
   TRAP     #0 ; enter supervisor state
   ORI      #$700,SR ; disable interrupts
   LEA      STODATIN,A0
   MOVE.W   2(A0),D1  ; get readpointer
   CMP.W    (A0),D1   ; compare with writepointer
   BEQ.S    NODATA
   MOVE.W   12(A0,D1.W),D0 ; read data from buffer
   ADDQ.W   #2,D1     ; increment readpointer
   CMPI.W   #SERQIN,D1
   BLT.S    GTS_L1
   MOVEQ    #0,D1
GTS_L1
   MOVE.W   D1,2(A0)  ; update readpointer
   ADD.L    #1,8(A0)  ; update number of characters read
   AND.W    #$FF,D0   ; cut off stop bits
   BRA.S    GTS_EXIT
NODATA:
   MOVEQ    #-1,D0    ; signal no data to calling programm
   MOVE.B   #$00,HANDSHAKE ; set DTR and RTS
GTS_EXIT
   ANDI     #0,SR    ; enter usermode and enable interrupts
   NOP
#endasm
}

/* --------------------------------------------------------------
            write data to serial output queue
   -------------------------------------------------------------- */
putser(d)
short int    d ;
{
#asm
   MOVE.W   8(A7),D0     ; read data from stack (C convention)
   MOVEM.L  D1/A0,-(A7)
   OR.W     #$300,D0     ; set two stop bits
WAIT_TSRE
   BTST     #12,SERDATR  ; test if transmit shift register is empty
   BEQ.S    WAIT_TSRE
WAIT_CTS
   BTST     #4,HANDSHAKE ; test CTS bit
   BNE.S    WAIT_CTS     ; wait for CTS bit
   MOVE.W   D0,SERDAT    ; write data to serial port
   MOVEQ    #1,D0        ; clear TBE bit to reset TSRE
;   MOVE.W   D0,INTREQ
   MOVEM.L  (A7)+,D1/A0
#endasm
}

/* Here are the main routines to support the serial port */
#asm
OLDVECTS:
   DS.L     4 ; Reserve space for old vectors
STODATIN:
   DC.W     0 ; initial writepointer
   DC.W     0 ; initial readpointer
   DC.L     0 ; count written characters
   DC.L     0 ; count characters read
   DS.B     SERQIN ; reserve space for received data
   DC.L     2 ; for savety

; This routine just switches on the Supervisormode
SUPER_ON:
   ADDQ.L   #2,A7
   RTS
; ---------------------------------------------------
;  Read the serial port after interrupt level 5 (RBF)
; ---------------------------------------------------
READSER:
   BTST     #11,INTREQR ; RBF Interrupt from serial port ?
   BEQ.S    GO_ON_L5
   MOVEM.L  D0/D1/A0,-(A7)
   MOVE.W   SERDATR,D0     ; get data
   LEA      STODATIN,A0
   MOVE.W   (A0),D1  ; get writepointer
   MOVE.W   D0,12(A0,D1.W)
   ADDQ.W   #2,D1
   CMPI.W   #SERQIN,D1
   BLT.S    RDS_L1
   MOVEQ    #0,D1
RDS_L1
   MOVE.W   D1,(A0)  ; update writepointer
   MOVE.L   4(A0),D0 ; get count of written data
   ADDQ.L   #1,D0    ; icrement by one
   MOVE.L   D0,4(A0) ; update counter
   SUB.L    8(A0),D0 ; subtract number of characters read
   CMP.L    #STOPSER,D0 ; stop transmission ?
   BLT.S    RDS_L2
   MOVE.B   #$F0,HANDSHAKE  ; clear DTR and RTS
RDS_L2
   CLR.W    D0
   BSET     #11,D0   ; clear RBF interrupt level 5 bit
   MOVE.W   D0,INTREQ
   MOVEM.L  (A7)+,D0/D1/A0
   RTE
GO_ON_L5:
   MOVE.L   OLDVECTS+4(PC),-(A7) ; Push old interrupt routine onto stack
   RTS      ; and call this routine
#endasm

/* ---------------------------------------------------------
   this routine should be allready present in the library,
   but it was not, so i write it by myself:
   --------------------------------------------------------- */
delay(n)
short int n ;
{
#asm
   MOVE.W   8(A7),D0     ; read data from stack (C convention)
   MOVEM.L  D1-D7/A0-A6,-(A7)
   MOVE.W   D0,-(A7)
   BSR      GET_DOS
   CLR.L    D1
   MOVE.W   (A7)+,D1
   JSR      -198(A6)    ; DOS function delay
   MOVEM.L  (A7)+,D1-D7/A0-A6
#endasm
}

#asm
; ----------------------------------------
; return base address of DOS library in A6
; ----------------------------------------
GET_DOS
   MOVE.L   DOS_Base(PC),A6
   MOVE.L   A6,D0    ; is this DOS_Base allready defined ?
   BNE.S    EX_GETDOS
   MOVE.L   4,A6    ; get EXEC base
   LEA      DOS_Name,A1
   JSR      OldOpenLibrary(A6)
   LEA      DOS_Base,A6
   MOVE.L   D0,(A6) ; save DOS_Base for next call
   MOVE.L   D0,A6   ; get DOS_Base in A6
EX_GETDOS
   RTS
DOS_Name:   DC.B  "dos.library",0
   NOP
DOS_Base:   DC.L  0
#endasm

