/***************************************************************************** * * $Id$ * Purpose ...............: Ymodem sender * ***************************************************************************** * Copyright (C) 1997-2004 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "../config.h" #include "../lib/mbselib.h" #include "../lib/mbse.h" #include "ttyio.h" #include "zmmisc.h" #include "transfer.h" #include "openport.h" #include "timeout.h" #include "term.h" #include "ymsend.h" #define MAX_BLOCK 8192 #define sendline(c) PUTCHAR((c & 0377)) FILE *input_f; char Crcflg; char Lastrx; int Fullname = 0; /* transmit full pathname */ int Filesleft; long Totalleft; long bytes_sent; int firstsec; int Optiong; /* Let it rip no wait for sector ACK's */ int Totsecs; /* total number of sectors this file */ char *txbuf; size_t blklen = 128; /* length of transmitted records */ int zmodem_requested = FALSE; static int no_unixmode; struct timeval starttime, endtime; struct timezone tz; long skipsize; extern int Rxtimeout; static int wctxpn(char *); static int getnak(void); static int wctx(long); static int wcputsec(char *, int, size_t); static size_t filbuf(char *, size_t); int ymsndfiles(down_list *lst, int use1k) { int maxrc = 0; down_list *tmpf; Syslog('+', "%s: start send files", protname()); txbuf = malloc(MAX_BLOCK); /* * Count files to transmit */ Totalleft = Filesleft = 0; for (tmpf = lst; tmpf; tmpf = tmpf->next) { if (tmpf->remote) { Filesleft++; Totalleft += tmpf->size; } } Syslog('x', "%s: %d files, size %d bytes", protname(), Filesleft, Totalleft); /* * This is not documented as auto start sequence, but a lot of terminal * programs seem to react on this. */ if (protocol == ZM_YMODEM) PUTSTR((char *)"rb\r"); for (tmpf = lst; tmpf && (maxrc < 2); tmpf = tmpf->next) { if (tmpf->remote) { bytes_sent = 0; skipsize = 0L; Totsecs = 0; switch (wctxpn(tmpf->remote)) { case TERROR: Syslog('x', "wctxpn returns error"); tmpf->failed = TRUE; maxrc = 2; break; case ZSKIP: Syslog('x', "wctxpn returns skip"); tmpf->failed = TRUE; break; case OK: gettimeofday(&starttime, &tz); if ((blklen == 128) && use1k) { Syslog('x', "%s: will use 1K blocks", protname()); blklen = 1024; } if (wctx(tmpf->size) == TERROR) { Syslog('+', "%s: wctx returned error", protname()); tmpf->failed = TRUE; } else { tmpf->sent = TRUE; gettimeofday(&endtime, &tz); Syslog('+', "%s: OK %s", protname(), transfertime(starttime, endtime, (unsigned long)tmpf->size - skipsize, TRUE)); } } } else if (maxrc == 0) { tmpf->failed = TRUE; } if (unlink(tmpf->remote)) Syslog('+', "%s: could not unlink %s", protname(), tmpf->remote); } if (maxrc == 2) { canit(1); purgeline(50); } else { if (protocol == ZM_YMODEM) { /* * Send empty filename to signal end of batch */ wctxpn((char *)""); } } if (txbuf) free(txbuf); txbuf = NULL; io_mode(0, 1); Syslog('+', "%s: end send files rc=%d", protname(), maxrc); return (maxrc < 2)?0:maxrc; } /* * generate and transmit pathname block consisting of * pathname (null terminated), * file length, mode time and file mode in octal * as provided by the Unix fstat call. * N.B.: modifies the passed name, may extend it! */ static int wctxpn(char *fname) { register char *p, *q; char *name2; struct stat f; name2 = alloca(PATH_MAX+1); if ((input_f = fopen(fname, "r"))) { fstat(fileno(input_f), &f); Syslog('+', "%s: send \"%s\"", protname(), MBSE_SS(fname)); Syslog('+', "%s: size %lu bytes, dated %s", protname(), (unsigned long)f.st_size, rfcdate(f.st_mtime)); if (protocol == ZM_XMODEM) { if (*fname) { sprintf(name2, "Sending %s, %ld blocks: ", fname, (long) (f.st_size >> 7)); PUTSTR(name2); Enter(1); } PUTSTR((char *)"Give your local XMODEM receive command now."); Enter(1); return OK; } } else { /* * Reset, this normally happens for the ymodem end of batch block. */ f.st_size = 0; f.st_mtime = 0; f.st_mode = 0; } if (!zmodem_requested) if (getnak()) { Syslog('+', "%s: timeout sending filename %s", protname(), MBSE_SS(fname)); return TERROR; } q = (char *) 0; for (p = fname, q = txbuf ; *p; ) if ((*q++ = *p++) == '/' && !Fullname) q = txbuf; *q++ = 0; p = q; while (q < (txbuf + MAX_BLOCK)) *q++ = 0; /* * note that we may lose some information here in case mode_t is wider than an * int. But i believe sending %lo instead of %o _could_ break compatability */ if ((input_f != stdin) && *fname) sprintf(p, "%lu %lo %o 0 %d %ld", (long) f.st_size, f.st_mtime, (unsigned int)((no_unixmode) ? 0 : f.st_mode), Filesleft, Totalleft); Totalleft -= f.st_size; if (--Filesleft <= 0) Totalleft = 0; if (Totalleft < 0) Totalleft = 0; purgeline(1); if (wcputsec(txbuf, 0, 128) == TERROR) { Syslog('+', "%s: failed to send filename", protname()); return TERROR; } return OK; } /* * Get NAK, C or G */ static int getnak(void) { int firstch; int tries = 0; Syslog('x', "getnak()"); Lastrx = 0; for (;;) { tries++; switch (firstch = GETCHAR(10)) { case TIMEOUT: Syslog('x', "getnak: timeout try %d", tries); /* 50 seconds are enough (was 30, 26-11-2004 MB) */ if (tries == 5) { Syslog('x', "Timeout on pathname"); return TRUE; } continue; case WANTG: Syslog('x', "getnak: got WANTG"); io_mode(0, 2); /* Set cbreak, XON/XOFF, etc. */ if (!Optiong) Syslog('+', "%s: using Ymodem-G", protname()); Optiong = TRUE; blklen=1024; Crcflg = TRUE; return FALSE; case WANTCRC: Syslog('x', "getnak: got WANTCRC"); Crcflg = TRUE; return FALSE; case NAK: Syslog('x', "getnak: got NAK"); return FALSE; case CAN: Syslog('x', "getnak: got CAN"); if ((firstch = GETCHAR(2)) == CAN && Lastrx == CAN) { Syslog('+', "%s: Receiver cancelled", protname()); return TRUE; } default: Syslog('x', "got %d", firstch); break; } Lastrx = firstch; } Syslog('x', "getnak: done"); } static int wctx(long bytes_total) { register size_t thisblklen; register int sectnum, attempts, firstch; firstsec=TRUE; thisblklen = blklen; Syslog('x', "wctx: file length=%ld, blklen=%d", bytes_total, blklen); while ((firstch = GETCHAR(Rxtimeout)) != NAK && firstch != WANTCRC && firstch != WANTG && firstch != TIMEOUT && firstch != CAN); if (firstch == CAN) { Syslog('+', "%s: receiver Cancelled during transfer", protname()); return TERROR; } if (firstch == WANTCRC) Crcflg = TRUE; if (firstch == WANTG) Crcflg = TRUE; sectnum = 0; for (;;) { if (bytes_total <= (bytes_sent + 896L)) thisblklen = 128; if ( !filbuf(txbuf, thisblklen)) break; if (wcputsec(txbuf, ++sectnum, thisblklen) == TERROR) return TERROR; bytes_sent += thisblklen; /* * Keep connections alive */ Nopper(); alarm_on(); } fclose(input_f); attempts = 0; do { purgeline(5); PUTCHAR(EOT); ++attempts; } while ((firstch = (GETCHAR(Rxtimeout)) != ACK) && attempts < RETRYMAX); if (attempts == RETRYMAX) { Syslog('x', "No ACK on EOT"); return TERROR; } else return OK; } static int wcputsec(char *buf, int sectnum, size_t cseclen) { int Checksum, wcj; char *cp; unsigned oldcrc; int firstch; int attempts; firstch = 0; /* part of logic to detect CAN CAN */ Syslog('x', "%s: wcputsec: sectnum %d, len %d, %s", protname(), sectnum, cseclen, Crcflg ? "crc":"checksum"); for (attempts = 0; attempts <= RETRYMAX; attempts++) { Lastrx = firstch; sendline(cseclen == 1024 ? STX:SOH); sendline(sectnum); sendline((-sectnum -1)); oldcrc = Checksum = 0; for (wcj = cseclen, cp = buf; --wcj >= 0; ) { sendline(*cp); oldcrc = updcrc16((0377& *cp), oldcrc); Checksum += *cp++; } if (Crcflg) { oldcrc = updcrc16(0, updcrc16(0, oldcrc)); sendline((int)oldcrc >> 8); sendline((int)oldcrc); } else { sendline(Checksum); } if (Optiong) { firstsec = FALSE; return OK; } firstch = GETCHAR(Rxtimeout); gotnak: switch (firstch) { case CAN: Syslog('x', "got CAN"); if(Lastrx == CAN) { cancan: Syslog('x', "Cancelled"); return TERROR; } break; case TIMEOUT: Syslog('x', "Timeout on sector ACK"); continue; case WANTCRC: if (firstsec) Crcflg = TRUE; case NAK: Syslog('+', "%s: got NAK on sector %d", protname(), sectnum); purgeline(20); continue; case ACK: firstsec=FALSE; Totsecs += (cseclen>>7); return OK; case TERROR: Syslog('x', "Got burst for sector ACK"); break; default: Syslog('x', "Got %02x for sector ACK", firstch); break; } for (;;) { Lastrx = firstch; if ((firstch = GETCHAR(Rxtimeout)) == TIMEOUT) break; if (firstch == NAK || firstch == WANTCRC) goto gotnak; if (firstch == CAN && Lastrx == CAN) goto cancan; } } Syslog('x', "Retry Count Exceeded"); return TERROR; } /* * fill buf with count chars padding with ^Z for CPM */ static size_t filbuf(char *buf, size_t count) { size_t m; m = read(fileno(input_f), buf, count); if (m <= 0) return 0; while (m < count) buf[m++] = 032; return count; }