home *** CD-ROM | disk | FTP | other *** search
- /*
- * The routines in this file are the private modules used to perform
- * XMODEM file reception. In addition to the files here, there are
- * also some common routine in _XMODEMC.C. The xmodem file transfers
- * are performed by a lot of different routines that perform a small
- * piece of the whole transfer. The status of the transfer is kept
- * in the xmodem parameter block that is passed back and forth by
- * all of these routines.
- *
- * The Greenleaf Comm Library
- *
- * Copyright (C) 1989-90 Greenleaf Software Inc. All Rights Reserved.
- *
- */
- #define LINT_ARGS
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "gf.h"
- #include "asiports.h"
- #include "xfer.h"
- #include "_xfer.h"
-
- /*
- * void _XmodemReceive(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This is the main module for the XMODEM transfer routine. It is in
- * charge of doing it all.
- *
- * SIDE EFFECTS
- *
- * File(s) may have been transfered.
- *
- * RETURNS
- *
- * The return from this program is in the xmodem parameter block
- * return status structure element. It is one of the XFER_RETURN
- * codes defined in XFER.H.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
- void GF_CONV _XmodemReceive(XFER *xmodem)
- {
- int done;
-
- xmodem->sending=FALSE;
- if (!_XferInitialize(xmodem)) /* These first few lines set up the */
- return; /* parameter block for an XMODEM xfer. */
- xmodem->block_number=1L;
-
- if (!_XferOpenFile(xmodem)) /* Next, I open the output file. */
- return;
- if (!_XmodemSendNAK(xmodem)) /* Now I start the Xmodem transfer off */
- return; /* by sending the initial NAK. */
- done=FALSE;
-
- while (!done) /* At this point I start a giant loop, reading in blocks */
- { /* until I hit an EOT, which means the whole deal is done*/
-
- if (!_XmodemReceiveBlock(xmodem))
- {
- _XferCleanup(xmodem);
- return;
- }
- switch (xmodem->x.xmodem.last_control_char) /* After reading the block*/
- { /* in, I process it depend*/
- case STX : /* ing on the type of */
- case SOH : /* buffer it is. */
- if (fwrite(xmodem->buffer,xmodem->current_block_size,1,xmodem->file) != 1)
- {
- xmodem->return_status=XFER_RETURN_FILE_ERROR;
- _XferCleanup(xmodem);
- return;
- }
- xmodem->byte_count += xmodem->current_block_size;
- if (xmodem->transfer_type <= XFER_TYPE_YMODEM)
- if (!_XmodemSendACK(xmodem)) /* I don't send this ACK if*/
- done=TRUE; /* the transfer type is *-G*/
- break;
- case EOT :
- _XmodemSendACK(xmodem);
- _XferMessage(xmodem,"End of file, cleaning up");
- done=TRUE;
- break;
- default :
- xmodem->return_status=XFER_RETURN_LOGIC_ERROR;
- done=TRUE;
- break;
- }
- }
- _XferCleanup(xmodem); /* I close my file and free the buffers now. */
- }
-
-
- /*
- * void _YmodemReceive(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * A YMODEM transfer is very similar to an XMODEM transfer. The main
- * difference is that before the transfer starts, the YMODEM sender
- * sends a special block 0 that has the filename in it. After the
- * receiver ACKS the file name, it looks like XMODEM-1K transfer takes
- * place. So this YMODEM reciever code performs a block 0 read, then
- * performs a regular XMODEM transfer. It keeps doing this over and
- * over until it either fails because of an error, or receives an
- * empty block 0, indicating that the sender is done.
- *
- * SIDE EFFECTS
- *
- * File(s) may have been transfered.
- *
- * RETURNS
- *
- * The return from this program is in the xmodem parameter block
- * return status structure element. It is one of the XFER_RETURN
- * codes defined in XFER.H.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- void GF_CONV _YmodemReceive(XFER *xmodem) /* The YMODEM receive routine has */
- { /* only one job to do, and that is*/
- XFER ymodem; /* to receive the special block 0.*/
- static char filename[65]; /* It can then pass all the real */
- int done; /* work of the file transfer on to*/
- /* the XMODEM receiver. */
-
- done=FALSE; /* I use the done flag to control */
- /* my exit from the receive loop. */
-
- ymodem=*xmodem; /* When I go off to receive my */
- ymodem.sending=FALSE; /* special block 0, I use my own */
- /* private parameter block. I set*/
- /* it up here to have the same */
- /* message and idle routines as */
- /* my caller specified. */
-
- while (!done)
- {
- ymodem.file_length= 0L; /* File size is 0 right now */
- if (!_XferInitialize(&ymodem)) /* First I initialize my special */
- return; /* parameter block, and set the */
- ymodem.block_number = 0L; /* block number to 0, instead of 1*/
-
- if (!_XmodemSendNAK(&ymodem)) /* Now I send the initial ACK to */
- return; /* get things started. */
-
- /* Now I try to receive my block 0*/
- if (!_XmodemReceiveBlock(&ymodem)) /* If I don't get it, things are */
- { /* bad, and I quit. */
- _XferCleanup(&ymodem);
- return;
- }
- if (!_XmodemSendACK(&ymodem)) /* After getting the first block, */
- { /* I go ahead an ACK it. If the */
- done=TRUE; /* ACK fails, I blow out of here. */
- continue;
- }
- if (ymodem.x.xmodem.last_control_char==SOH ||
- ymodem.x.xmodem.last_control_char==STX)
- {
- strcpy(filename,ymodem.buffer); /* At this point I copy the file */
- xmodem->filename=filename; /* name passed in block 0 into my*/
- if (strlen(filename)==0) /* XFER parameter block. If the */
- done=TRUE; /* filename is legnth 0, it means*/
- else /* the remote end is done, and I */
- { /* exit. Otherwise, I call the */
- /* XMODEM receiver and tell it to*/
- /* read in this file. */
- xmodem->file_length = atol(ymodem.buffer+strlen(filename)+1);
- _XmodemReceive(xmodem);
- timer(TICKS_PER_SECOND*1);
- if (xmodem->return_status != XFER_RETURN_SUCCESS)
- done=TRUE;
- }
- }
- else
- done=TRUE;
- _XferCleanup(&ymodem); /* After the transfer is complete, I clean*/
- } /* up my private parameter block before */
- } /* starting again. */
-
- /*
- * int _XmodemReceiveBlock(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This is a utility routine that is called to read in a block of data in
- * XMODEM format. If it returns a TRUE, it means it read in and checked
- * out a full block of data. The caller is free at that point to act
- * on the data.
- *
- * Note that most of the routines called by this guy can return a FALSE,
- * but not have it be a fatal error. The FALSE returns are all checked
- * to see if the return_status structure element has been changed. If
- * not, we don't process this buffer, but we do go back and retry.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it read in a block, FALSE otherwise.
- * If FALSE, the error code is returned in the return_status structure
- * element.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemReceiveBlock(XFER *xmodem)
- {
- /*
- * I don't return until I have read in the block, or exceeded the
- * maximum retry count.
- */
- while (xmodem->error_count < XMODEM_MAX_ERRORS)
- {
- if (_XferAbortKeyPressed(xmodem)) /* Return if the user hits */
- return(FALSE); /* the abort key. */
- if (!_XmodemGetFirstCharacter(xmodem)) /* Here I read the first char*/
- /* of a block. */
- if (xmodem->return_status) /* If the error counter has */
- return(FALSE); /* overflowed, I exit */
- else
- continue;
- switch (xmodem->x.xmodem.last_control_char)
- {
- case EOT : /* An EOT by itself is a */
- return(TRUE); /* valid buffer, return it*/
-
- case STX : /* STX and SOH signify */
- xmodem->current_block_size=1024; /* normal data buffers. I */
- break; /* just set up the buffer */
- case SOH : /* size for each of these */
- xmodem->current_block_size=128; /* guys and move on to */
- break; /* read in the data. */
-
- default : /* Anything else is bad! */
- xmodem->return_status=XFER_RETURN_LOGIC_ERROR;
- return(FALSE);
- }
- if (!_XmodemGetBlockNumber(xmodem)) /* Here I read in the two bytes */
- if (xmodem->return_status) /* of the block number. If there*/
- return(FALSE); /* was an error reading them in, */
- else /* check to see if it was fatal, */
- continue; /* and then either try again or */
- /* exit. */
- if (!_XmodemReadDataForBlock(xmodem)) /* Now I call this routine to */
- if (xmodem->return_status) /* read in either 128 or 1024 */
- return(FALSE); /* bytes of data. Once again */
- else /* I have to handle both the */
- continue; /* fatal and non-fatal errors.*/
-
- if (!_XmodemGetChecksum(xmodem)) /* Now I go out and read in the */
- if (xmodem->return_status) /* one or two byte checksum. */
- return(FALSE);
- else
- continue;
- if (!_XmodemCheckBlockNumber(xmodem)) /* Now I check to see if this */
- if (xmodem->return_status) /* blocknumber is the one I */
- return(FALSE); /* expect it to be. I handle */
- else /* any error by either trying */
- continue; /* again or returning. */
- if (!_XmodemVerifyChecksum(xmodem)) /* Here I check to see if the */
- if (xmodem->return_status) /* checksum is good. If not, I */
- return(FALSE); /* either try again or call the */
- else /* whole thing off. */
- continue;
- /*
- * If I made it here, this is a good block. Increment the block number and
- * continue return the TRUE flag to the caller.
- */
- _XferMessage(xmodem,"Read %d byte block %ld",xmodem->current_block_size,xmodem->block_number);
- xmodem->block_number++;
- /*
- * One last detail. If I am in a Ymodem file transfer, I might be at the
- * end of the file, in which case I might want to truncate the size of this
- * buffer so the length ends up being exactly what I want.
- */
- if ( xmodem->transfer_type == XFER_TYPE_YMODEM ||
- xmodem->transfer_type == XFER_TYPE_YMODEM_G )
- if ( xmodem->file_length > 0L )
- if ( (xmodem->byte_count + xmodem->current_block_size ) > xmodem->file_length )
- xmodem->current_block_size = ( int ) ( xmodem->file_length - xmodem->byte_count );
- return(TRUE);
- }
- /*
- * If I fall down here, it means the maximum retry count was exceeded.
- */
- xmodem->return_status=XFER_RETURN_TOO_MANY_ERRORS;
- return(FALSE);
- }
-
- /*
- * int _XmodemCheckBlockNumber(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to check the validity of a block number. It
- * has three basic possiblities. First, it could that this is the
- * correct block number. If so, I return a TRUE. Secondly, I could
- * be getting a second copy of the last block. This routine takes it
- * upon itself to ACK a duplicate block, but it returns a FALSE, so the
- * calling routine won't try to process this block. Finally, it could
- * just be a bad number, in which case a NAK goes out, and a FALSE is
- * returned.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it read in a good block, FALSE otherwise.
- * If FALSE, the error count is incremented, and the calling process can
- * assume that the block has been NAKed down here.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemCheckBlockNumber(XFER *xmodem)
- {
- /*
- * This first check is for the duplicate block possibility.
- */
- if (xmodem->current_block_number == (int) ((xmodem->block_number - 1) & 0xff))
- {
- if (!_XmodemSendACK(xmodem))
- return(FALSE);
- xmodem->error_count++;
- _XferMessage(xmodem,"Duplicate block %ld",xmodem->block_number);
- return(FALSE);
- }
- /*
- * The second check is for a bad block number.
- */
- if (xmodem->current_block_number != (int) (xmodem->block_number & 0xff))
- {
- if (!_XmodemSendNAK(xmodem))
- return(FALSE);
- xmodem->error_count++;
- _XferMessage(xmodem,"Bad block number waiting for number %ld",xmodem->block_number);
- return(FALSE);
- }
- /*
- * I fall through here if everything is okay.
- */
- return(TRUE);
- }
-
- /*
- * int _XmodemVerifyChecksum(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to see if the checksum read in for the
- * current block agrees with the actual checksum for the block.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if the checksum matches up with the block.
- * Otherwise, the error counter is bumped and a FALSE is returned.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemVerifyChecksum(XFER *xmodem)
- {
- int checksum;
- int i;
-
- if (xmodem->x.xmodem.crc_mode)
- {
- if (glcrc(xmodem->current_block_size,0,xmodem->buffer) != xmodem->current_block_checksum)
- {
- if (!_XmodemSendNAK(xmodem))
- return(FALSE);
- xmodem->error_count++;
- _XferMessage(xmodem,"Bad CRC on block number %ld",xmodem->block_number);
- return(FALSE);
- }
- }
- else /* Checksum is the additive type */
- {
- checksum=0;
- for (i=0;i<xmodem->current_block_size;i++) /* Calculate the checksum */
- checksum += xmodem->buffer[i];
-
- if ((checksum & 0xff) != xmodem->current_block_checksum)
- {
- if (!_XmodemSendNAK(xmodem))
- return(FALSE);
- xmodem->error_count++;
- _XferMessage(xmodem,"Bad checksum on block number %ld",xmodem->block_number);
- return(FALSE);
- }
- }
- return(TRUE);
- }
-
- /*
- * int _XmodemGetChecksum(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to read in the checksum, which can be either
- * one or two bytes. The resulting integer is stored in the transfer
- * parameter block.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully reads in the one or two
- * byte checksum. Otherwise, the error counter is bumped and a
- * FALSE is returned.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemGetChecksum(XFER *xmodem)
- {
- int checksum_1;
- int checksum_2;
-
- checksum_1=asigetc_timed(xmodem->port,18); /* First I read in byte 1 */
- if (xmodem->x.xmodem.crc_mode) /* Then, if we are in CRC */
- checksum_2=asigetc_timed(xmodem->port,18);/* I read in byte 2. Else*/
- else /* I just set it to be a 0*/
- checksum_2=0;
- if (checksum_1 < 0 || checksum_2 < 0) /* If either read generated an*/
- { /* error, I handle it */
- if (!_XmodemSendNAK(xmodem))
- return(FALSE);
- if (!_XferFlushInput(xmodem))
- return(FALSE);
- xmodem->error_count++;
- return(FALSE);
- }
- xmodem->current_block_checksum=checksum_1;
- if (xmodem->x.xmodem.crc_mode)
- {
- xmodem->current_block_checksum <<= 8;
- xmodem->current_block_checksum += checksum_2;
- }
- return(TRUE);
- }
-
- /*
- * int _XmodemReadDataForBlock(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to read in all of the bytes for the current
- * block. This is pretty simple.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully reads in all of
- * the bytes. If there are any errors on reading data it flushes
- * the input buffer and returns a FALSE.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemReadDataForBlock(XFER *xmodem)
- {
- int i;
- int c;
-
- for (i=0;i<xmodem->current_block_size;i++) /* The block size was set up */
- { /* earlier when the first */
- c=asigetc_timed(xmodem->port,18); /* character was read in. */
- if (c < 0) /* All this routine has to do */
- { /* is read in all the chars */
- if (!_XmodemSendNAK(xmodem)) /* and make sure that none of */
- return(FALSE); /* the reads generate an error*/
- if (!_XferFlushInput(xmodem)) /* Errors are handled by */
- return(FALSE); /* flushing the buffer, bump- */
- xmodem->error_count++; /* ing the error count, and */
- return(FALSE); /* returning a FALSE. */
- }
- xmodem->buffer[i]= (char) c;
- }
- return(TRUE);
- }
-
- /*
- * int _XmodemGetBlockNumber(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * The block number for an XMODEM block is two consecutive bytes. The first
- * byte is the straight block number, and the second one is it inverse.
- * This routine reads in those two bytes, and makes sure that the second
- * one is the inverse of the first.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully reads in both of the
- * bytes, and the second is the inverse of the first. If it fails
- * any of these tests, it sends a NAK, increments the error count, and
- * returns a FALSE.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemGetBlockNumber(XFER *xmodem)
- {
- int block;
- int inverse_block;
-
- block=asigetc_timed(xmodem->port,18);
- inverse_block=asigetc_timed(xmodem->port,18);
- if (block < 0 || inverse_block < 0 || block != (~inverse_block & 0xff))
- {
- if (!_XmodemSendNAK(xmodem))
- return(FALSE);
- if (!_XferFlushInput(xmodem))
- return(FALSE);
- xmodem->error_count++;
- _XferMessage(xmodem,"Bad block numbers %02x %02x",block,inverse_block);
- return(FALSE);
- }
- xmodem->current_block_number=block;
- return(TRUE);
- }
-
- /*
- * int _XmodemGetFirstCharacter(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine's job is to get the first character for an XMODEM packet.
- * It has a timeout timer, and sits waiting while the timeout timer is
- * running. Eventually it either gets a character or times out. It then
- * processes that event. A valid first character can be an EOT, SOH, or
- * STX. Any of these will return a TRUE. Just about anything else will
- * cause a FALSE to be returned. If the maximum retry count is exceeded,
- * this return will set the return_status structure element, causing the
- * XMODEM driver to abort.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully reads in a good first
- * character. In the event of a CAN-CAN, or a maximum retry count exceeded,
- * it will set the return_status error flag, and return a FALSE.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemGetFirstCharacter(XFER *xmodem)
- {
- int timeout_timer;
- int last_char;
- int c;
-
- if (xmodem->block_number < 2) /* I set up the timeout timer */
- timeout_timer=XMODEM_STARTUP_TIMEOUT; /* here. Note that blocks 0 & */
- else /* and 1 get a different timer */
- timeout_timer=XMODEM_RECEIVE_TIMEOUT; /* than all the others. */
-
- while ((c=asigetc_timed(xmodem->port,18)) < 0)/* Now I sit in this loop, &*/
- { /* wait for either a timeout*/
- if (_XferAbortKeyPressed(xmodem)) /* a character, or the abort*/
- return(FALSE); /* key to be pressed. */
- if (--timeout_timer == 0)
- break;
- }
- if (timeout_timer)
- {
- last_char=xmodem->x.xmodem.last_control_char;/* If I get here, it means*/
- xmodem->x.xmodem.last_control_char=c; /* I read in a character, */
- switch (c) /* and can go ahead and */
- { /* process it. */
- case CAN :
- if (last_char==CAN)
- {
- xmodem->return_status=XFER_RETURN_REMOTE_ABORT;
- return(FALSE);
- }
- break;
- case EOT :
- return(TRUE);
- case SOH :
- return(TRUE);
- case STX :
- return(TRUE);
- default :
- while (asigetc_timed(xmodem->port,18) >= 0)
- if (_XferAbortKeyPressed(xmodem))
- return(FALSE);
- break;
- }
- }
- xmodem->error_count++;
- if (xmodem->error_count > 2 && xmodem->block_number==1 &&
- xmodem->transfer_type <= XFER_TYPE_YMODEM)
- xmodem->x.xmodem.crc_mode=FALSE;
- _XmodemSendNAK(xmodem);
- if (xmodem->error_count >= XMODEM_MAX_ERRORS)
- xmodem->return_status=XFER_RETURN_PACKET_TIMEOUT;
- return(FALSE);
- }
-
- /*
- * int _XmodemSendNAK(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to send a NAK, which is not necessarily as simple
- * as it may sound. The NAK character can be one of three different chars,
- * depending both on the protocol, and what block is being sent. After
- * the NAK is sent, a check is made for transmission errors.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully sends the character,
- * otherwise it flags the fatal error and returns a FALSE.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemSendNAK(XFER *xmodem)
- {
- int nak_char;
- int stat;
-
- if (xmodem->block_number < 2)
- {
- if (xmodem->transfer_type > XFER_TYPE_YMODEM)
- nak_char='G';
- else if (xmodem->x.xmodem.crc_mode)
- nak_char='C';
- else
- nak_char=NAK;
- }
- else
- nak_char=NAK;
- stat=asiputc(xmodem->port,nak_char);
- if (stat == ASSUCCESS)
- return(TRUE);
- xmodem->return_status=XFER_RETURN_CANT_SEND_NAK;
- return(FALSE);
- }
-
- /*
- * int _XmodemSendACK(XFER *xmodem)
- *
- * ARGUMENTS
- *
- * xmodem: A pointer to the parameter block used to perform the
- * transfer.
- *
- * DESCRIPTION
- *
- * This routine is called to send an ACK. After the ACK is sent,
- * a check is made for transmission errors.
- *
- * SIDE EFFECTS
- *
- *
- * RETURNS
- *
- * This routine returns TRUE if it successfully sends the character,
- * otherwise it flags the fatal error and returns a FALSE.
- *
- * AUTHOR
- * Mark Nelson 28-Aug-1989 20:57:40.92
- *
- * MODIFICATIONS
- *
- */
-
- int GF_CONV _XmodemSendACK(XFER *xmodem)
- {
- if (asiputc(xmodem->port,ACK)==ASSUCCESS)
- return(TRUE);
- xmodem->return_status=XFER_RETURN_CANT_SEND_ACK;
- return(FALSE);
- }
-