blob: e04896ae2c1177013f57d230ff3a470f2b936848 [file] [log] [blame]
/*
lsz - send files with x/y/zmodem
Copyright (C) until 1988 Chuck Forsberg (Omen Technology INC)
Copyright (C) 1994 Matt Porter, Michael D. Black
Copyright (C) 1996, 1997 Uwe Ohse
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
originally written by Chuck Forsberg
*/
#include "zglobal.h"
/* char *getenv(); */
#define SS_NORMAL 0
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#ifndef R_OK
# define R_OK 4
#endif
#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP)
# include <sys/mman.h>
size_t mm_size;
void *mm_addr=NULL;
#else
# undef HAVE_MMAP
#endif
#include "timing.h"
#include "long-options.h"
#include "xstrtoul.h"
#include "error.h"
#ifndef STRICT_PROTOTYPES
extern time_t time();
extern char *strerror();
#endif
#ifndef HAVE_ERRNO_DECLARATION
extern int errno;
#endif
unsigned Baudrate=2400; /* Default, should be set by first mode() call */
unsigned Txwindow; /* Control the size of the transmitted window */
unsigned Txwspac; /* Spacing between zcrcq requests */
unsigned Txwcnt; /* Counter used to space ack requests */
size_t Lrxpos; /* Receiver's last reported offset */
int errors;
enum zm_type_enum protocol;
int under_rsh=FALSE;
extern int turbo_escape;
static int no_unixmode;
int Canseek=1; /* 1: can; 0: only rewind, -1: neither */
static int zsendfile __P ((struct zm_fileinfo *zi, const char *buf, size_t blen));
static int getnak __P ((void));
static int wctxpn __P ((struct zm_fileinfo *));
static int wcs __P ((const char *oname, const char *remotename));
static size_t zfilbuf __P ((struct zm_fileinfo *zi));
static size_t filbuf __P ((char *buf, size_t count));
static int getzrxinit __P ((void));
static int calc_blklen __P ((long total_sent));
static int sendzsinit __P ((void));
static int wctx __P ((struct zm_fileinfo *));
static int zsendfdata __P ((struct zm_fileinfo *));
static int getinsync __P ((struct zm_fileinfo *, int flag));
static void countem __P ((int argc, char **argv));
static void chkinvok __P ((const char *s));
static void usage __P ((int exitcode, const char *what));
static int zsendcmd __P ((const char *buf, size_t blen));
static void saybibi __P ((void));
static int wcsend __P ((int argc, char *argp[]));
static int wcputsec __P ((char *buf, int sectnum, size_t cseclen));
static void usage1 __P ((int exitcode));
#ifdef ENABLE_SYSLOG
#define DO_SYSLOG(message) do { \
if (enable_syslog) { \
const char *shortname; \
if (!zi->fname) \
shortname="no.name"; \
else { \
shortname=strrchr(zi->fname,'/'); \
if (!shortname) \
shortname=zi->fname; \
else \
shortname++; \
} \
lsyslog message ; \
} \
} while(0)
#else
#define DO_SYSLOG(message) do { } while(0)
#endif
#define ZSDATA(x,y,z) \
do { if (Crc32t) {zsda32(x,y,z); } else {zsdata(x,y,z);}} while(0)
#ifdef HAVE_MMAP
#define DATAADR (mm_addr ? ((char *)mm_addr)+zi->bytes_sent : txbuf)
#else
#define DATAADR (txbuf)
#endif
int Filesleft;
long Totalleft;
size_t buffersize=16384;
#ifdef HAVE_MMAP
int use_mmap=1;
#endif
/*
* Attention string to be executed by receiver to interrupt streaming data
* when an error is detected. A pause (0336) may be needed before the
* ^C (03) or after it.
*/
#ifdef READCHECK
char Myattn[] = { 0 };
#else
char Myattn[] = { 03, 0336, 0 };
#endif
FILE *input_f;
#define MAX_BLOCK 8192
char *txbuf;
long vpos = 0; /* Number of bytes read from file */
char Lastrx;
char Crcflg;
int Verbose=0;
int Restricted=0; /* restricted; no /.. or ../ in filenames */
int Quiet=0; /* overrides logic that would otherwise set verbose */
int Ascii=0; /* Add CR's for brain damaged programs */
int Fullname=0; /* transmit full pathname */
int Unlinkafter=0; /* Unlink file after it is sent */
int Dottoslash=0; /* Change foo.bar.baz to foo/bar/baz */
int firstsec;
int errcnt=0; /* number of files unreadable */
size_t blklen=128; /* length of transmitted records */
int Optiong; /* Let it rip no wait for sector ACK's */
int Totsecs; /* total number of sectors this file */
int Filcnt=0; /* count of number of files opened */
int Lfseen=0;
unsigned Rxbuflen = 16384; /* Receiver's max buffer length */
unsigned Tframlen = 0; /* Override for tx frame length */
unsigned blkopt=0; /* Override value for zmodem blklen */
int Rxflags = 0;
int Rxflags2 = 0;
size_t bytcnt;
int Wantfcs32 = TRUE; /* want to send 32 bit FCS */
char Lzconv; /* Local ZMODEM file conversion request */
char Lzmanag; /* Local ZMODEM file management request */
int Lskipnocor;
char Lztrans;
char zconv; /* ZMODEM file conversion request */
char zmanag; /* ZMODEM file management request */
char ztrans; /* ZMODEM file transport request */
int command_mode; /* Send a command, then exit. */
int Cmdtries = 11;
int Cmdack1; /* Rx ACKs command, then do it */
int Exitcode;
int enable_timesync=0;
size_t Lastsync; /* Last offset to which we got a ZRPOS */
int Beenhereb4; /* How many times we've been ZRPOS'd same place */
int no_timeout=FALSE;
size_t max_blklen=1024;
size_t start_blklen=0;
int zmodem_requested;
time_t stop_time=0;
char *tcp_server_address=0;
int tcp_socket=-1;
int tcp_flag=0;
int error_count;
#define OVERHEAD 18
#define OVER_ERR 20
#define MK_STRING(x) #x
#ifdef ENABLE_SYSLOG
# if defined(ENABLE_SYSLOG_FORCE) || defined(ENABLE_SYSLOG_DEFAULT)
int enable_syslog=TRUE;
# else
int enable_syslog=FALSE;
# endif
#endif
jmp_buf intrjmp; /* For the interrupt on RX CAN */
static long min_bps;
static long min_bps_time;
static int io_mode_fd=0;
static int zrqinits_sent=0;
static int play_with_sigint=0;
/* called by signal interrupt or terminate to clean things up */
RETSIGTYPE
bibi (int n)
{
canit(STDOUT_FILENO);
fflush (stdout);
io_mode (io_mode_fd,0);
if (n == 99)
error (0, 0, _ ("io_mode(,2) in rbsb.c not implemented\n"));
else
error (0, 0, _ ("caught signal %d; exiting"), n);
if (n == SIGQUIT)
abort ();
exit (128 + n);
}
/* Called when ZMODEM gets an interrupt (^C) */
static RETSIGTYPE
onintr(int n)
{
signal(SIGINT, SIG_IGN);
n++; /* use it */
longjmp(intrjmp, -1);
}
int Zctlesc; /* Encode control characters */
const char *program_name = "sz";
int Zrwindow = 1400; /* RX window size (controls garbage count) */
static struct option const long_options[] =
{
{"append", no_argument, NULL, '+'},
{"twostop", no_argument, NULL, '2'},
{"try-8k", no_argument, NULL, '8'},
{"start-8k", no_argument, NULL, '9'},
{"try-4k", no_argument, NULL, '4'},
{"start-4k", no_argument, NULL, '5'},
{"ascii", no_argument, NULL, 'a'},
{"binary", no_argument, NULL, 'b'},
{"bufsize", required_argument, NULL, 'B'},
{"cmdtries", required_argument, NULL, 'C'},
{"command", required_argument, NULL, 'c'},
{"immediate-command", required_argument, NULL, 'i'},
{"dot-to-slash", no_argument, NULL, 'd'},
{"full-path", no_argument, NULL, 'f'},
{"escape", no_argument, NULL, 'e'},
{"rename", no_argument, NULL, 'E'},
{"help", no_argument, NULL, 'h'},
{"crc-check", no_argument, NULL, 'H'},
{"1024", no_argument, NULL, 'k'},
{"1k", no_argument, NULL, 'k'},
{"packetlen", required_argument, NULL, 'L'},
{"framelen", required_argument, NULL, 'l'},
{"min-bps", required_argument, NULL, 'm'},
{"min-bps-time", required_argument, NULL, 'M'},
{"newer", no_argument, NULL, 'n'},
{"newer-or-longer", no_argument, NULL, 'N'},
{"16-bit-crc", no_argument, NULL, 'o'},
{"disable-timeouts", no_argument, NULL, 'O'},
{"disable-timeout", no_argument, NULL, 'O'}, /* i can't get it right */
{"protect", no_argument, NULL, 'p'},
{"resume", no_argument, NULL, 'r'},
{"restricted", no_argument, NULL, 'R'},
{"quiet", no_argument, NULL, 'q'},
{"stop-at", required_argument, NULL, 's'},
{"syslog", optional_argument, NULL , 2},
{"timesync", no_argument, NULL, 'S'},
{"timeout", required_argument, NULL, 't'},
{"turbo", no_argument, NULL, 'T'},
{"unlink", no_argument, NULL, 'u'},
{"unrestrict", no_argument, NULL, 'U'},
{"verbose", no_argument, NULL, 'v'},
{"windowsize", required_argument, NULL, 'w'},
{"xmodem", no_argument, NULL, 'X'},
{"ymodem", no_argument, NULL, 1},
{"zmodem", no_argument, NULL, 'Z'},
{"overwrite", no_argument, NULL, 'y'},
{"overwrite-or-skip", no_argument, NULL, 'Y'},
{"delay-startup", required_argument, NULL, 4},
{"tcp-server", no_argument, NULL, 6},
{"tcp-client", required_argument, NULL, 7},
{"no-unixmode", no_argument, NULL, 8},
{NULL, 0, NULL, 0}
};
static void
show_version(void)
{
printf ("%s (%s) %s\n", program_name, PACKAGE, VERSION);
}
int
main(int argc, char **argv)
{
char *cp;
int npats;
int dm;
int i;
int stdin_files;
char **patts;
int c;
const char *Cmdstr=NULL; /* Pointer to the command string */
unsigned int startup_delay=0;
if (((cp = getenv("ZNULLS")) != NULL) && *cp)
Znulls = atoi(cp);
if (((cp=getenv("SHELL"))!=NULL) && (strstr(cp, "rsh") || strstr(cp, "rksh")
|| strstr(cp, "rbash") || strstr(cp,"rshell")))
{
under_rsh=TRUE;
Restricted=1;
}
if ((cp=getenv("ZMODEM_RESTRICTED"))!=NULL)
Restricted=1;
from_cu();
chkinvok(argv[0]);
#ifdef ENABLE_SYSLOG
openlog(program_name,LOG_PID,ENABLE_SYSLOG);
#endif
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
parse_long_options (argc, argv, show_version, usage1);
Rxtimeout = 600;
while ((c = getopt_long (argc, argv,
"2+48abB:C:c:dfeEghHi:kL:l:m:M:NnOopRrqsSt:TUuvw:XYy",
long_options, (int *) 0))!=EOF)
{
unsigned long int tmp;
char *tmpptr;
enum strtol_error s_err;
switch (c)
{
case 0:
break;
case '+': Lzmanag = ZF1_ZMAPND; break;
case '2': Twostop = TRUE; break;
case '8':
if (max_blklen==8192)
start_blklen=8192;
else
max_blklen=8192;
break;
case '9': /* this is a longopt .. */
start_blklen=8192;
max_blklen=8192;
break;
case '4':
if (max_blklen==4096)
start_blklen=4096;
else
max_blklen=4096;
break;
case '5': /* this is a longopt .. */
start_blklen=4096;
max_blklen=4096;
break;
case 'a': Lzconv = ZCNL; Ascii = TRUE; break;
case 'b': Lzconv = ZCBIN; break;
case 'B':
if (0==strcmp(optarg,"auto"))
buffersize= (size_t) -1;
else
buffersize=strtol(optarg,NULL,10);
#ifdef HAVE_MMAP
use_mmap=0;
#endif
break;
case 'C':
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
Cmdtries = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("command tries"), s_err);
break;
case 'i':
Cmdack1 = ZCACK1;
/* **** FALL THROUGH TO **** */
case 'c':
command_mode = TRUE;
Cmdstr = optarg;
break;
case 'd':
++Dottoslash;
/* **** FALL THROUGH TO **** */
case 'f': Fullname=TRUE; break;
case 'e': Zctlesc = 1; break;
case 'E': Lzmanag = ZF1_ZMCHNG; break;
case 'h': usage(0,NULL); break;
case 'H': Lzmanag = ZF1_ZMCRC; break;
case 'k': start_blklen=1024; break;
case 'L':
s_err = xstrtoul (optarg, NULL, 0, &tmp, "ck");
blkopt = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("packetlength"), s_err);
if (blkopt<24 || blkopt>MAX_BLOCK)
{
char meld[256];
sprintf(meld,
_("packetlength out of range 24..%ld"),
(long) MAX_BLOCK);
usage(2,meld);
}
break;
case 'l':
s_err = xstrtoul (optarg, NULL, 0, &tmp, "ck");
Tframlen = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("framelength"), s_err);
if (Tframlen<32 || Tframlen>MAX_BLOCK)
{
char meld[256];
sprintf(meld,
_("framelength out of range 32..%ld"),
(long) MAX_BLOCK);
usage(2,meld);
}
break;
case 'm':
s_err = xstrtoul (optarg, &tmpptr, 0, &tmp, "km");
min_bps = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("min_bps"), s_err);
if (min_bps<0)
usage(2,_("min_bps must be >= 0"));
break;
case 'M':
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
min_bps_time = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("min_bps_time"), s_err);
if (min_bps_time<=1)
usage(2,_("min_bps_time must be > 1"));
break;
case 'N': Lzmanag = ZF1_ZMNEWL; break;
case 'n': Lzmanag = ZF1_ZMNEW; break;
case 'o': Wantfcs32 = FALSE; break;
case 'O': no_timeout = TRUE; break;
case 'p': Lzmanag = ZF1_ZMPROT; break;
case 'r':
if (Lzconv == ZCRESUM)
Lzmanag = ZF1_ZMCRC;
else
Lzconv = ZCRESUM;
break;
case 'R': Restricted = TRUE; break;
case 'q': Quiet=TRUE; Verbose=0; break;
case 's':
if (isdigit((unsigned char) (*optarg))) {
struct tm *tm;
time_t t;
int hh,mm;
char *nex;
hh = strtoul (optarg, &nex, 10);
if (hh>23)
usage(2,_("hour to large (0..23)"));
if (*nex!=':')
usage(2, _("unparsable stop time\n"));
nex++;
mm = strtoul (optarg, &nex, 10);
if (mm>59)
usage(2,_("minute to large (0..59)"));
t=time(NULL);
tm=localtime(&t);
tm->tm_hour=hh;
tm->tm_min=hh;
stop_time=mktime(tm);
if (stop_time<t)
stop_time+=86400L; /* one day more */
if (stop_time - t <10)
usage(2,_("stop time to small"));
} else {
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
stop_time = tmp + time(0);
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("stop-at"), s_err);
if (tmp<10)
usage(2,_("stop time to small"));
}
break;
case 'S': enable_timesync=1; break;
case 'T': turbo_escape=1; break;
case 't':
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
Rxtimeout = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("timeout"), s_err);
if (Rxtimeout<10 || Rxtimeout>1000)
usage(2,_("timeout out of range 10..1000"));
break;
case 'u': ++Unlinkafter; break;
case 'U':
if (!under_rsh)
Restricted=0;
else
error(1,0,
_("security violation: can't do that under restricted shell\n"));
break;
case 'v': ++Verbose; break;
case 'w':
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
Txwindow = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("window size"), s_err);
if (Txwindow < 256)
Txwindow = 256;
Txwindow = (Txwindow/64) * 64;
Txwspac = Txwindow/4;
if (blkopt > Txwspac
|| (!blkopt && Txwspac < MAX_BLOCK))
blkopt = Txwspac;
break;
case 'X': protocol=ZM_XMODEM; break;
case 1: protocol=ZM_YMODEM; break;
case 'Z': protocol=ZM_ZMODEM; break;
case 'Y':
Lskipnocor = TRUE;
/* **** FALLL THROUGH TO **** */
case 'y':
Lzmanag = ZF1_ZMCLOB; break;
case 2:
#ifdef ENABLE_SYSLOG
# ifndef ENABLE_SYSLOG_FORCE
if (optarg && (!strcmp(optarg,"off") || !strcmp(optarg,"no")))
{
if (under_rsh)
error(0,0, _("cannot turnoff syslog"));
else
enable_syslog=FALSE;
}
else
enable_syslog=TRUE;
# else
error(0,0, _("cannot turnoff syslog"));
# endif
#endif
break;
case 4:
s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL);
startup_delay = tmp;
if (s_err != LONGINT_OK)
STRTOL_FATAL_ERROR (optarg, _("startup delay"), s_err);
break;
case 6:
tcp_flag=2;
break;
case 7:
tcp_flag=3;
tcp_server_address=(char *)strdup(optarg);
if (!tcp_server_address) {
error(1,0,_("out of memory"));
}
break;
case 8: no_unixmode=1; break;
default:
usage (2,NULL);
break;
}
}
if (getuid()!=geteuid()) {
error(1,0,
_("this program was never intended to be used setuid\n"));
}
txbuf=malloc(MAX_BLOCK);
if (!txbuf) error(1,0,_("out of memory"));
zsendline_init();
if (start_blklen==0) {
if (protocol == ZM_ZMODEM) {
start_blklen=1024;
if (Tframlen) {
start_blklen=max_blklen=Tframlen;
}
}
else
start_blklen=128;
}
if (argc<2)
usage(2,_("need at least one file to send"));
if (startup_delay)
sleep(startup_delay);
#ifdef HAVE_SIGINTERRUPT
/* we want interrupted system calls to fail and not to be restarted. */
siginterrupt(SIGALRM,1);
#endif
npats = argc - optind;
patts=&argv[optind];
if (npats < 1 && !command_mode)
usage(2,_("need at least one file to send"));
if (command_mode && Restricted) {
printf(_("Can't send command in restricted mode\n"));
exit(1);
}
if (Fromcu && !Quiet) {
if (Verbose == 0)
Verbose = 2;
}
vfile("%s %s\n", program_name, VERSION);
if (tcp_flag==2) {
char buf[256];
#ifdef MAXHOSTNAMELEN
char hn[MAXHOSTNAMELEN];
#else
char hn[256];
#endif
char *p,*q;
int d;
/* tell receiver to receive via tcp */
d=tcp_server(buf);
p=strchr(buf+1,'<');
p++;
q=strchr(p,'>');
*q=0;
if (gethostname(hn,sizeof(hn))==-1) {
error(1,0, _("hostname too long\n"));
}
fprintf(stdout,"connect with lrz --tcp-client \"%s:%s\"\n",hn,p);
fflush(stdout);
/* ok, now that this file is sent we can switch to tcp */
tcp_socket=tcp_accept(d);
dup2(tcp_socket,0);
dup2(tcp_socket,1);
}
if (tcp_flag==3) {
char buf[256];
char *p;
p=strchr(tcp_server_address,':');
if (!p)
error(1,0, _("illegal server address\n"));
*p++=0;
sprintf(buf,"[%s] <%s>\n",tcp_server_address,p);
fprintf(stdout,"connecting to %s\n",buf);
fflush(stdout);
/* we need to switch to tcp mode */
tcp_socket=tcp_connect(buf);
dup2(tcp_socket,0);
dup2(tcp_socket,1);
}
{
/* we write max_blocklen (data) + 18 (ZModem protocol overhead)
* + escape overhead (about 4 %), so buffer has to be
* somewhat larger than max_blklen
*/
char *s=malloc(max_blklen+1024);
if (!s)
{
zperr(_("out of memory"));
exit(1);
}
#ifdef SETVBUF_REVERSED
setvbuf(stdout,_IOFBF,s,max_blklen+1024);
#else
setvbuf(stdout,s,_IOFBF,max_blklen+1024);
#endif
}
blklen=start_blklen;
for (i=optind,stdin_files=0;i<argc;i++) {
if (0==strcmp(argv[i],"-"))
stdin_files++;
}
if (stdin_files>1) {
usage(1,_("can read only one file from stdin"));
} else if (stdin_files==1) {
io_mode_fd=1;
}
io_mode(io_mode_fd,1);
readline_setup(io_mode_fd, 128, 256);
if (signal(SIGINT, bibi) == SIG_IGN)
signal(SIGINT, SIG_IGN);
else {
signal(SIGINT, bibi);
play_with_sigint=1;
}
signal(SIGTERM, bibi);
signal(SIGPIPE, bibi);
signal(SIGHUP, bibi);
if ( protocol!=ZM_XMODEM) {
if (protocol==ZM_ZMODEM) {
printf("rz\r");
fflush(stdout);
}
countem(npats, patts);
if (protocol == ZM_ZMODEM) {
/* throw away any input already received. This doesn't harm
* as we invite the receiver to send it's data again, and
* might be useful if the receiver has already died or
* if there is dirt left if the line
*/
#ifdef HAVE_SELECT
struct timeval t;
unsigned char throwaway;
fd_set f;
#endif
purgeline(io_mode_fd);
#ifdef HAVE_SELECT
t.tv_sec = 0;
t.tv_usec = 0;
FD_ZERO(&f);
FD_SET(io_mode_fd,&f);
while (select(1,&f,NULL,NULL,&t)) {
if (0==read(io_mode_fd,&throwaway,1)) /* EOF ... */
break;
}
#endif
purgeline(io_mode_fd);
stohdr(0L);
if (command_mode)
Txhdr[ZF0] = ZCOMMAND;
zshhdr(ZRQINIT, Txhdr);
zrqinits_sent++;
#if defined(ENABLE_TIMESYNC)
if (Rxflags2 != ZF1_TIMESYNC)
/* disable timesync if there are any flags we don't know.
* dsz/gsz seems to use some other flags! */
enable_timesync=FALSE;
if (Rxflags2 & ZF1_TIMESYNC && enable_timesync) {
Totalleft+=6; /* TIMESYNC never needs more */
Filesleft++;
}
#endif
}
}
fflush(stdout);
if (Cmdstr) {
if (getzrxinit()) {
Exitcode=0200; canit(STDOUT_FILENO);
}
else if (zsendcmd(Cmdstr, strlen(Cmdstr)+1)) {
Exitcode=0200; canit(STDOUT_FILENO);
}
} else if (wcsend(npats, patts)==ERROR) {
Exitcode=0200;
canit(STDOUT_FILENO);
}
fflush(stdout);
io_mode(io_mode_fd,0);
if (Exitcode)
dm=Exitcode;
else if (errcnt)
dm=1;
else
dm=0;
if (Verbose)
{
fputs("\r\n",stderr);
if (dm)
fputs(_("Transfer incomplete\n"),stderr);
else
fputs(_("Transfer complete\n"),stderr);
}
exit(dm);
/*NOTREACHED*/
}
static int
send_pseudo(const char *name, const char *data)
{
char *tmp;
const char *p;
int ret=0; /* ok */
size_t plen;
int fd;
int lfd;
p = getenv ("TMPDIR");
if (!p)
p = getenv ("TMP");
if (!p)
p = "/tmp";
tmp=malloc(PATH_MAX+1);
if (!tmp)
error(1,0,_("out of memory"));
plen=strlen(p);
memcpy(tmp,p,plen);
tmp[plen++]='/';
lfd=0;
do {
if (lfd++==10) {
free(tmp);
vstringf (_ ("send_pseudo %s: cannot open tmpfile %s: %s"),
name, tmp, strerror (errno));
vstring ("\r\n");
return 1;
}
sprintf(tmp+plen,"%s.%lu.%d",name,(unsigned long) getpid(),lfd);
fd=open(tmp,O_WRONLY|O_CREAT|O_EXCL,0700);
/* is O_EXCL guaranted to not follow symlinks?
* I don`t know ... so be careful
*/
if (fd!=-1) {
struct stat st;
if (0!=lstat(tmp,&st)) {
vstringf (_ ("send_pseudo %s: cannot lstat tmpfile %s: %s"),
name, tmp, strerror (errno));
vstring ("\r\n");
unlink(tmp);
close(fd);
fd=-1;
} else {
if (S_ISLNK(st.st_mode)) {
vstringf (_ ("send_pseudo %s: avoiding symlink trap"),name);
vstring ("\r\n");
unlink(tmp);
close(fd);
fd=-1;
}
}
}
} while (fd==-1);
if (write(fd,data,strlen(data))!=(signed long) strlen(data)
|| close(fd)!=0) {
vstringf (_ ("send_pseudo %s: cannot write to tmpfile %s: %s"),
name, tmp, strerror (errno));
vstring ("\r\n");
free(tmp);
return 1;
}
if (wcs (tmp,name) == ERROR) {
if (Verbose)
vstringf (_ ("send_pseudo %s: failed"),name);
else {
if (Verbose)
vstringf (_ ("send_pseudo %s: ok"),name);
Filcnt--;
}
vstring ("\r\n");
ret=1;
}
unlink (tmp);
free(tmp);
return ret;
}
static int
wcsend (int argc, char *argp[])
{
int n;
Crcflg = FALSE;
firstsec = TRUE;
bytcnt = (size_t) -1;
for (n = 0; n < argc; ++n) {
Totsecs = 0;
if (wcs (argp[n],NULL) == ERROR)
return ERROR;
}
#if defined(ENABLE_TIMESYNC)
if (Rxflags2 & ZF1_TIMESYNC && enable_timesync) {
/* implement Peter Mandrellas extension */
char buf[60];
time_t t = time (NULL);
struct tm *tm = localtime (&t); /* sets timezone */
strftime (buf, sizeof (buf) - 1, "%H:%M:%S", tm);
if (Verbose) {
vstring ("\r\n");
vstringf (_("Answering TIMESYNC at %s"),buf);
}
#if defined(HAVE_TIMEZONE_VAR)
sprintf(buf+strlen(buf),"%ld\r\n", timezone / 60);
if (Verbose)
vstringf (" (%s %ld)\r\n", _ ("timezone"), timezone / 60);
#else
if (Verbose)
vstringf (" (%s)\r\n", _ ("timezone unknown"));
#endif
send_pseudo("/$time$.t",buf);
}
#endif
Totsecs = 0;
if (Filcnt == 0) { /* bitch if we couldn't open ANY files */
#if 0
/* i *really* do not like this */
if (protocol != ZM_XMODEM) {
const char *Cmdstr; /* Pointer to the command string */
command_mode = TRUE;
Cmdstr = "echo \"lsz: Can't open any requested files\"";
if (getnak ()) {
Exitcode = 0200;
canit(STDOUT_FILENO);
}
if (!zmodem_requested)
canit(STDOUT_FILENO);
else if (zsendcmd (Cmdstr, 1 + strlen (Cmdstr))) {
Exitcode = 0200;
canit(STDOUT_FILENO);
}
Exitcode = 1;
return OK;
}
#endif
canit(STDOUT_FILENO);
vstring ("\r\n");
vstringf (_ ("Can't open any requested files."));
vstring ("\r\n");
return ERROR;
}
if (zmodem_requested)
saybibi ();
else if (protocol != ZM_XMODEM) {
struct zm_fileinfo zi;
char *pa;
pa=alloca(PATH_MAX+1);
*pa='\0';
zi.fname = pa;
zi.modtime = 0;
zi.mode = 0;
zi.bytes_total = 0;
zi.bytes_sent = 0;
zi.bytes_received = 0;
zi.bytes_skipped = 0;
wctxpn (&zi);
}
return OK;
}
static int
wcs(const char *oname, const char *remotename)
{
#if !defined(S_ISDIR)
int c;
#endif
struct stat f;
char *name;
struct zm_fileinfo zi;
#ifdef HAVE_MMAP
int dont_mmap_this=0;
#endif
#ifdef ENABLE_SYSLOG
const char *shortname;
shortname=strrchr(oname,'/');
if (shortname)
shortname++;
else
shortname=oname;
#endif
if (Restricted) {
/* restrict pathnames to current tree or uucppublic */
if ( strstr(oname, "../")
#ifdef PUBDIR
|| (oname[0]== '/' && strncmp(oname, MK_STRING(PUBDIR),
strlen(MK_STRING(PUBDIR))))
#endif
) {
canit(STDOUT_FILENO);
vchar('\r');
error(1,0,
_("security violation: not allowed to upload from %s"),oname);
}
}
if (0==strcmp(oname,"-")) {
char *p=getenv("ONAME");
name=alloca(PATH_MAX+1);
if (p) {
strcpy(name, p);
} else {
sprintf(name, "s%lu.lsz", (unsigned long) getpid());
}
input_f=stdin;
#ifdef HAVE_MMAP
dont_mmap_this=1;
#endif
} else if ((input_f=fopen(oname, "r"))==NULL) {
int e=errno;
error(0,e, _("cannot open %s"),oname);
++errcnt;
return OK; /* pass over it, there may be others */
} else {
name=alloca(PATH_MAX+1);
strcpy(name, oname);
}
#ifdef HAVE_MMAP
if (!use_mmap || dont_mmap_this)
#endif
{
static char *s=NULL;
static size_t last_length=0;
struct stat st;
if (fstat(fileno(input_f),&st)==-1)
st.st_size=1024*1024;
if (buffersize==(size_t) -1 && s) {
if ((size_t) st.st_size > last_length) {
free(s);
s=NULL;
last_length=0;
}
}
if (!s && buffersize) {
last_length=16384;
if (buffersize==(size_t) -1) {
if (st.st_size>0)
last_length=st.st_size;
} else
last_length=buffersize;
/* buffer whole pages */
last_length=(last_length+4095)&0xfffff000;
s=malloc(last_length);
if (!s) {
zpfatal(_("out of memory"));
exit(1);
}
}
if (s) {
#ifdef SETVBUF_REVERSED
setvbuf(input_f,_IOFBF,s,last_length);
#else
setvbuf(input_f,s,_IOFBF,last_length);
#endif
}
}
vpos = 0;
/* Check for directory or block special files */
fstat(fileno(input_f), &f);
#if defined(S_ISDIR)
if (S_ISDIR(f.st_mode) || S_ISBLK(f.st_mode)) {
#else
c = f.st_mode & S_IFMT;
if (c == S_IFDIR || c == S_IFBLK) {
#endif
error(0,0, _("is not a file: %s"),name);
fclose(input_f);
return OK;
}
if (remotename) {
/* disqualify const */
union {
const char *c;
char *s;
} cheat;
cheat.c=remotename;
zi.fname=cheat.s;
} else
zi.fname=name;
zi.modtime=f.st_mtime;
zi.mode=f.st_mode;
#if defined(S_ISFIFO)
zi.bytes_total= (S_ISFIFO(f.st_mode)) ? DEFBYTL : f.st_size;
#else
zi.bytes_total= c == S_IFIFO ? DEFBYTL : f.st_size;
#endif
zi.bytes_sent=0;
zi.bytes_received=0;
zi.bytes_skipped=0;
zi.eof_seen=0;
timing(1,NULL);
++Filcnt;
switch (wctxpn(&zi)) {
case ERROR:
#ifdef ENABLE_SYSLOG
if (enable_syslog)
lsyslog(LOG_INFO, _("%s/%s: error occured"),protname(),shortname);
#endif
return ERROR;
case ZSKIP:
error(0,0, _("skipped: %s"),name);
#ifdef ENABLE_SYSLOG
if (enable_syslog)
lsyslog(LOG_INFO, _("%s/%s: skipped"),protname(),shortname);
#endif
return OK;
}
if (!zmodem_requested && wctx(&zi)==ERROR)
{
#ifdef ENABLE_SYSLOG
if (enable_syslog)
lsyslog(LOG_INFO, _("%s/%s: error occured"),protname(),shortname);
#endif
return ERROR;
}
if (Unlinkafter)
unlink(oname);
if (Verbose > 1
#ifdef ENABLE_SYSLOG
|| enable_syslog
#endif
) {
long bps;
double d=timing(0,NULL);
if (d==0) /* can happen if timing() uses time() */
d=0.5;
bps=zi.bytes_sent/d;
vchar('\r');
if (Verbose > 1)
vstringf(_("Bytes Sent:%7ld BPS:%-8ld \n"),
(long) zi.bytes_sent,bps);
#ifdef ENABLE_SYSLOG
if (enable_syslog)
lsyslog(LOG_INFO, "%s/%s: %ld Bytes, %ld BPS",shortname,
protname(), (long) zi.bytes_sent,bps);
#endif
}
return 0;
}
/*
* 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(struct zm_fileinfo *zi)
{
register char *p, *q;
char *name2;
struct stat f;
name2=alloca(PATH_MAX+1);
if (protocol==ZM_XMODEM) {
if (Verbose && *zi->fname && fstat(fileno(input_f), &f)!= -1) {
vstringf(_("Sending %s, %ld blocks: "),
zi->fname, (long) (f.st_size>>7));
}
vstringf(_("Give your local XMODEM receive command now."));
vstring("\r\n");
return OK;
}
if (!zmodem_requested)
if (getnak()) {
vfile("getnak failed");
DO_SYSLOG((LOG_INFO, "%s/%s: getnak failed",
shortname,protname()));
return ERROR;
}
q = (char *) 0;
if (Dottoslash) { /* change . to . */
for (p=zi->fname; *p; ++p) {
if (*p == '/')
q = p;
else if (*p == '.')
*(q=p) = '/';
}
if (q && strlen(++q) > 8) { /* If name>8 chars */
q += 8; /* make it .ext */
strcpy(name2, q); /* save excess of name */
*q = '.';
strcpy(++q, name2); /* add it back */
}
}
for (p=zi->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 (!Ascii && (input_f!=stdin) && *zi->fname && fstat(fileno(input_f), &f)!= -1)
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);
if (Verbose)
vstringf(_("Sending: %s\n"),txbuf);
Totalleft -= f.st_size;
if (--Filesleft <= 0)
Totalleft = 0;
if (Totalleft < 0)
Totalleft = 0;
/* force 1k blocks if name won't fit in 128 byte block */
if (txbuf[125])
blklen=1024;
else { /* A little goodie for IMP/KMD */
txbuf[127] = (f.st_size + 127) >>7;
txbuf[126] = (f.st_size + 127) >>15;
}
if (zmodem_requested)
return zsendfile(zi,txbuf, 1+strlen(p)+(p-txbuf));
if (wcputsec(txbuf, 0, 128)==ERROR) {
vfile("wcputsec failed");
DO_SYSLOG((LOG_INFO, "%s/%s: wcputsec failed",
shortname,protname()));
return ERROR;
}
return OK;
}
static int
getnak(void)
{
int firstch;
int tries=0;
Lastrx = 0;
for (;;) {
tries++;
switch (firstch = READLINE_PF(100)) {
case ZPAD:
if (getzrxinit())
return ERROR;
Ascii = 0; /* Receiver does the conversion */
return FALSE;
case TIMEOUT:
/* 30 seconds are enough */
if (tries==3) {
zperr(_("Timeout on pathname"));
return TRUE;
}
/* don't send a second ZRQINIT _directly_ after the
* first one. Never send more then 4 ZRQINIT, because
* omen rz stops if it saw 5 of them */
if ((zrqinits_sent>1 || tries>1) && zrqinits_sent<4) {
/* if we already sent a ZRQINIT we are using zmodem
* protocol and may send further ZRQINITs
*/
stohdr(0L);
zshhdr(ZRQINIT, Txhdr);
zrqinits_sent++;
}
continue;
case WANTG:
io_mode(io_mode_fd,2); /* Set cbreak, XON/XOFF, etc. */
Optiong = TRUE;
blklen=1024;
case WANTCRC:
Crcflg = TRUE;
case NAK:
return FALSE;
case CAN:
if ((firstch = READLINE_PF(20)) == CAN && Lastrx == CAN)
return TRUE;
default:
break;
}
Lastrx = firstch;
}
}
static int
wctx(struct zm_fileinfo *zi)
{
register size_t thisblklen;
register int sectnum, attempts, firstch;
firstsec=TRUE; thisblklen = blklen;
vfile("wctx:file length=%ld", (long) zi->bytes_total);
while ((firstch=READLINE_PF(Rxtimeout))!=NAK && firstch != WANTCRC
&& firstch != WANTG && firstch!=TIMEOUT && firstch!=CAN)
;
if (firstch==CAN) {
zperr(_("Receiver Cancelled"));
return ERROR;
}
if (firstch==WANTCRC)
Crcflg=TRUE;
if (firstch==WANTG)
Crcflg=TRUE;
sectnum=0;
for (;;) {
if (zi->bytes_total <= (zi->bytes_sent + 896L))
thisblklen = 128;
if ( !filbuf(txbuf, thisblklen))
break;
if (wcputsec(txbuf, ++sectnum, thisblklen)==ERROR)
return ERROR;
zi->bytes_sent += thisblklen;
}
fclose(input_f);
attempts=0;
do {
purgeline(io_mode_fd);
sendline(EOT);
flushmo();
++attempts;
} while ((firstch=(READLINE_PF(Rxtimeout)) != ACK) && attempts < RETRYMAX);
if (attempts == RETRYMAX) {
zperr(_("No ACK on EOT"));
return ERROR;
}
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 */
if (Verbose>1) {
vchar('\r');
if (protocol==ZM_XMODEM) {
vstringf(_("Xmodem sectors/kbytes sent: %3d/%2dk"), Totsecs, Totsecs/8 );
} else {
vstringf(_("Ymodem sectors/kbytes sent: %3d/%2dk"), Totsecs, Totsecs/8 );
}
}
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=updcrc((0377& *cp), oldcrc);
checksum += *cp++;
}
if (Crcflg) {
oldcrc=updcrc(0,updcrc(0,oldcrc));
sendline((int)oldcrc>>8);
sendline((int)oldcrc);
}
else
sendline(checksum);
flushmo();
if (Optiong) {
firstsec = FALSE; return OK;
}
firstch = READLINE_PF(Rxtimeout);
gotnak:
switch (firstch) {
case CAN:
if(Lastrx == CAN) {
cancan:
zperr(_("Cancelled")); return ERROR;
}
break;
case TIMEOUT:
zperr(_("Timeout on sector ACK")); continue;
case WANTCRC:
if (firstsec)
Crcflg = TRUE;
case NAK:
zperr(_("NAK on sector")); continue;
case ACK:
firstsec=FALSE;
Totsecs += (cseclen>>7);
return OK;
case ERROR:
zperr(_("Got burst for sector ACK")); break;
default:
zperr(_("Got %02x for sector ACK"), firstch); break;
}
for (;;) {
Lastrx = firstch;
if ((firstch = READLINE_PF(Rxtimeout)) == TIMEOUT)
break;
if (firstch == NAK || firstch == WANTCRC)
goto gotnak;
if (firstch == CAN && Lastrx == CAN)
goto cancan;
}
}
zperr(_("Retry Count Exceeded"));
return ERROR;
}
/* fill buf with count chars padding with ^Z for CPM */
static size_t
filbuf(char *buf, size_t count)
{
int c;
size_t m;
if ( !Ascii) {
m = read(fileno(input_f), buf, count);
if (m <= 0)
return 0;
while (m < count)
buf[m++] = 032;
return count;
}
m=count;
if (Lfseen) {
*buf++ = 012; --m; Lfseen = 0;
}
while ((c=getc(input_f))!=EOF) {
if (c == 012) {
*buf++ = 015;
if (--m == 0) {
Lfseen = TRUE; break;
}
}
*buf++ =c;
if (--m == 0)
break;
}
if (m==count)
return 0;
else
while (m--!=0)
*buf++ = CPMEOF;
return count;
}
/* Fill buffer with blklen chars */
static size_t
zfilbuf (struct zm_fileinfo *zi)
{
size_t n;
n = fread (txbuf, 1, blklen, input_f);
if (n < blklen)
zi->eof_seen = 1;
else {
/* save one empty paket in case file ends ob blklen boundary */
int c = getc(input_f);
if (c != EOF || !feof(input_f))
ungetc(c, input_f);
else
zi->eof_seen = 1;
}
return n;
}
static void
usage1 (int exitcode)
{
usage (exitcode, NULL);
}
static void
usage(int exitcode, const char *what)
{
FILE *f=stdout;
if (exitcode)
{
if (what)
fprintf(stderr, "%s: %s\n",program_name,what);
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
exit(exitcode);
}
fprintf(f, _("%s version %s\n"), program_name,
VERSION);
fprintf(f,_("Usage: %s [options] file ...\n"),
program_name);
fprintf(f,_(" or: %s [options] -{c|i} COMMAND\n"),program_name);
fputs(_("Send file(s) with ZMODEM/YMODEM/XMODEM protocol\n"),f);
fputs(_(
" (X) = option applies to XMODEM only\n"
" (Y) = option applies to YMODEM only\n"
" (Z) = option applies to ZMODEM only\n"
),f);
/* splitted into two halves for really bad compilers */
fputs(_(
" -+, --append append to existing destination file (Z)\n"
" -2, --twostop use 2 stop bits\n"
" -4, --try-4k go up to 4K blocksize\n"
" --start-4k start with 4K blocksize (doesn't try 8)\n"
" -8, --try-8k go up to 8K blocksize\n"
" --start-8k start with 8K blocksize\n"
" -a, --ascii ASCII transfer (change CR/LF to LF)\n"
" -b, --binary binary transfer\n"
" -B, --bufsize N buffer N bytes (N==auto: buffer whole file)\n"
" -c, --command COMMAND execute remote command COMMAND (Z)\n"
" -C, --command-tries N try N times to execute a command (Z)\n"
" -d, --dot-to-slash change '.' to '/' in pathnames (Y/Z)\n"
" --delay-startup N sleep N seconds before doing anything\n"
" -e, --escape escape all control characters (Z)\n"
" -E, --rename force receiver to rename files it already has\n"
" -f, --full-path send full pathname (Y/Z)\n"
" -i, --immediate-command CMD send remote CMD, return immediately (Z)\n"
" -h, --help print this usage message\n"
" -k, --1k send 1024 byte packets (X)\n"
" -L, --packetlen N limit subpacket length to N bytes (Z)\n"
" -l, --framelen N limit frame length to N bytes (l>=L) (Z)\n"
" -m, --min-bps N stop transmission if BPS below N\n"
" -M, --min-bps-time N for at least N seconds (default: 120)\n"
),f);
fputs(_(
" -n, --newer send file if source newer (Z)\n"
" -N, --newer-or-longer send file if source newer or longer (Z)\n"
" -o, --16-bit-crc use 16 bit CRC instead of 32 bit CRC (Z)\n"
" -O, --disable-timeouts disable timeout code, wait forever\n"
" -p, --protect protect existing destination file (Z)\n"
" -r, --resume resume interrupted file transfer (Z)\n"
" -R, --restricted restricted, more secure mode\n"
" -q, --quiet quiet (no progress reports)\n"
" -s, --stop-at {HH:MM|+N} stop transmission at HH:MM or in N seconds\n"
" --tcp-server open socket, wait for connection (Z)\n"
" --tcp-client ADDR:PORT open socket, connect to ... (Z)\n"
" -u, --unlink unlink file after transmission\n"
" -U, --unrestrict turn off restricted mode (if allowed to)\n"
" -v, --verbose be verbose, provide debugging information\n"
" -w, --windowsize N Window is N bytes (Z)\n"
" -X, --xmodem use XMODEM protocol\n"
" -y, --overwrite overwrite existing files\n"
" -Y, --overwrite-or-skip overwrite existing files, else skip\n"
" --ymodem use YMODEM protocol\n"
" -Z, --zmodem use ZMODEM protocol\n"
"\n"
"short options use the same arguments as the long ones\n"
),f);
exit(exitcode);
}
/*
* Get the receiver's init parameters
*/
static int
getzrxinit(void)
{
static int dont_send_zrqinit=1;
int old_timeout=Rxtimeout;
int n;
struct stat f;
size_t rxpos;
int timeouts=0;
Rxtimeout=100; /* 10 seconds */
/* XXX purgeline(io_mode_fd); this makes _real_ trouble. why? -- uwe */
for (n=10; --n>=0; ) {
/* we might need to send another zrqinit in case the first is
* lost. But *not* if getting here for the first time - in
* this case we might just get a ZRINIT for our first ZRQINIT.
* Never send more then 4 ZRQINIT, because
* omen rz stops if it saw 5 of them.
*/
if (zrqinits_sent<4 && n!=10 && !dont_send_zrqinit) {
zrqinits_sent++;
stohdr(0L);
zshhdr(ZRQINIT, Txhdr);
}
dont_send_zrqinit=0;
switch (zgethdr(Rxhdr, 1,&rxpos)) {
case ZCHALLENGE: /* Echo receiver's challenge numbr */
stohdr(rxpos);
zshhdr(ZACK, Txhdr);
continue;
case ZCOMMAND: /* They didn't see our ZRQINIT */
/* ??? Since when does a receiver send ZCOMMAND? -- uwe */
continue;
case ZRINIT:
Rxflags = 0377 & Rxhdr[ZF0];
Rxflags2 = 0377 & Rxhdr[ZF1];
Txfcs32 = (Wantfcs32 && (Rxflags & CANFC32));
{
int old=Zctlesc;
Zctlesc |= Rxflags & TESCCTL;
/* update table - was initialised to not escape */
if (Zctlesc && !old)
zsendline_init();
}
Rxbuflen = (0377 & Rxhdr[ZP0])+((0377 & Rxhdr[ZP1])<<8);
if ( !(Rxflags & CANFDX))
Txwindow = 0;
vfile("Rxbuflen=%d Tframlen=%d", Rxbuflen, Tframlen);
if ( play_with_sigint)
signal(SIGINT, SIG_IGN);
io_mode(io_mode_fd,2); /* Set cbreak, XON/XOFF, etc. */
#ifndef READCHECK
/* Use MAX_BLOCK byte frames if no sample/interrupt */
if (Rxbuflen < 32 || Rxbuflen > MAX_BLOCK) {
Rxbuflen = MAX_BLOCK;
vfile("Rxbuflen=%d", Rxbuflen);
}
#endif
/* Override to force shorter frame length */
if (Tframlen && Rxbuflen > Tframlen)
Rxbuflen = Tframlen;
if ( !Rxbuflen)
Rxbuflen = 1024;
vfile("Rxbuflen=%d", Rxbuflen);
/* If using a pipe for testing set lower buf len */
fstat(0, &f);
#if defined(S_ISCHR)
if (! (S_ISCHR(f.st_mode))) {
#else
if ((f.st_mode & S_IFMT) != S_IFCHR) {
#endif
Rxbuflen = MAX_BLOCK;
}
/*
* If input is not a regular file, force ACK's to
* prevent running beyond the buffer limits
*/
if ( !command_mode) {
fstat(fileno(input_f), &f);
#if defined(S_ISREG)
if (!(S_ISREG(f.st_mode))) {
#else
if ((f.st_mode & S_IFMT) != S_IFREG) {
#endif
Canseek = -1;
/* return ERROR; */
}
}
/* Set initial subpacket length */
if (blklen < 1024) { /* Command line override? */
if (Baudrate > 300)
blklen = 256;
if (Baudrate > 1200)
blklen = 512;
if (Baudrate > 2400)
blklen = 1024;
}
if (Rxbuflen && blklen>Rxbuflen)
blklen = Rxbuflen;
if (blkopt && blklen > blkopt)
blklen = blkopt;
vfile("Rxbuflen=%d blklen=%d", Rxbuflen, blklen);
vfile("Txwindow = %u Txwspac = %d", Txwindow, Txwspac);
Rxtimeout=old_timeout;
return (sendzsinit());
case ZCAN:
case TIMEOUT:
if (timeouts++==0)
continue; /* force one other ZRQINIT to be sent */
return ERROR;
case ZRQINIT:
if (Rxhdr[ZF0] == ZCOMMAND)
continue;
default:
zshhdr(ZNAK, Txhdr);
continue;
}
}
return ERROR;
}
/* Send send-init information */
static int
sendzsinit(void)
{
int c;
if (Myattn[0] == '\0' && (!Zctlesc || (Rxflags & TESCCTL)))
return OK;
errors = 0;
for (;;) {
stohdr(0L);
if (Zctlesc) {
Txhdr[ZF0] |= TESCCTL; zshhdr(ZSINIT, Txhdr);
}
else
zsbhdr(ZSINIT, Txhdr);
ZSDATA(Myattn, 1+strlen(Myattn), ZCRCW);
c = zgethdr(Rxhdr, 1,NULL);
switch (c) {
case ZCAN:
return ERROR;
case ZACK:
return OK;
default:
if (++errors > 19)
return ERROR;
continue;
}
}
}
/* Send file name and related info */
static int
zsendfile(struct zm_fileinfo *zi, const char *buf, size_t blen)
{
unsigned long crc;
size_t rxpos;
/* we are going to send a ZFILE. There cannot be much useful
* stuff in the line right now (*except* ZCAN?).
*/
#if 0
purgeline(io_mode_fd); /* might possibly fix stefan glasers problems */
#endif
for (;;) {
int gotblock;
int gotchar;
Txhdr[ZF0] = Lzconv; /* file conversion request */
Txhdr[ZF1] = Lzmanag; /* file management request */
if (Lskipnocor)
Txhdr[ZF1] |= ZF1_ZMSKNOLOC;
Txhdr[ZF2] = Lztrans; /* file transport request */
Txhdr[ZF3] = 0;
zsbhdr(ZFILE, Txhdr);
ZSDATA(buf, blen, ZCRCW);
again:
gotblock = zgethdr(Rxhdr, 1, &rxpos);
switch (gotblock) {
case ZRINIT:
while ((gotchar = READLINE_PF(50)) > 0)
if (gotchar == ZPAD) {
goto again;
}
/* **** FALL THRU TO **** */
default:
continue;
case ZRQINIT: /* remote site is sender! */
if (Verbose)
vstringf(_("got ZRQINIT"));
DO_SYSLOG((LOG_INFO, "%s/%s: got ZRQINIT - sz talks to sz",
shortname,protname()));
return ERROR;
case ZCAN:
if (Verbose)
vstringf(_("got ZCAN"));
DO_SYSLOG((LOG_INFO, "%s/%s: got ZCAN - receiver canceled",
shortname,protname()));
return ERROR;
case TIMEOUT:
DO_SYSLOG((LOG_INFO, "%s/%s: got TIMEOUT",
shortname,protname()));
return ERROR;
case ZABORT:
DO_SYSLOG((LOG_INFO, "%s/%s: got ZABORT",
shortname,protname()));
return ERROR;
case ZFIN:
DO_SYSLOG((LOG_INFO, "%s/%s: got ZFIN",
shortname,protname()));
return ERROR;
case ZCRC:
crc = 0xFFFFFFFFL;
#ifdef HAVE_MMAP
if (use_mmap && !mm_addr)
{
struct stat st;
/* mmap on files of 0 length can give 0 as result .. under linux 2.2.9 at least */
if (fstat (fileno (input_f), &st) == 0 && st.st_size!=0) {
mm_size = st.st_size;
mm_addr = mmap (0, mm_size, PROT_READ,
MAP_SHARED, fileno (input_f), 0);
if ((caddr_t) mm_addr == (caddr_t) - 1)
mm_addr = NULL;
else {
fclose (input_f);
input_f = NULL;
}
}
}
if (mm_addr) {
size_t i;
size_t count;
char *p=mm_addr;
count=(rxpos < mm_size && rxpos > 0)? rxpos: mm_size;
for (i=0;i<count;i++,p++) {
crc = UPDC32(*p, crc);
}
crc = ~crc;
} else
#endif
if (Canseek >= 0) {
if (rxpos==0) {
struct stat st;
if (0==fstat(fileno(input_f),&st)) {
rxpos=st.st_size;
} else
rxpos=-1;
}
while (rxpos-- && ((gotchar = getc(input_f)) != EOF))
crc = UPDC32(gotchar, crc);
crc = ~crc;
clearerr(input_f); /* Clear EOF */
fseek(input_f, 0L, 0);
}
stohdr(crc);
zsbhdr(ZCRC, Txhdr);
goto again;
case ZSKIP:
if (input_f) {
fclose(input_f);
input_f=NULL;
}
#ifdef HAVE_MMAP
else if (mm_addr) {
munmap(mm_addr,mm_size);
mm_addr=NULL;
}
#endif
vfile("receiver skipped");
DO_SYSLOG((LOG_INFO, "%s/%s: receiver skipped",
shortname, protname()));
return ZSKIP;
case ZRPOS:
/*
* Suppress zcrcw request otherwise triggered by
* lastsync==bytcnt
*/
#ifdef HAVE_MMAP
if (!mm_addr)
#endif
if (rxpos && fseek(input_f, (long) rxpos, 0)) {
int er=errno;
vfile("fseek failed: %s", strerror(er));
DO_SYSLOG((LOG_INFO, "%s/%s: fseek failed: %s",
shortname, protname(), strerror(er)));
return ERROR;
}
if (rxpos)
zi->bytes_skipped=rxpos;
bytcnt = zi->bytes_sent = rxpos;
Lastsync = rxpos -1;
return zsendfdata(zi);
}
}
}
/* Send the data in the file */
static int
zsendfdata (struct zm_fileinfo *zi)
{
static int c;
int newcnt;
static int junkcount; /* Counts garbage chars received by TX */
static size_t last_txpos = 0;
static long last_bps = 0;
static long not_printed = 0;
static long total_sent = 0;
static time_t low_bps=0;
#ifdef HAVE_MMAP
if (use_mmap && !mm_addr)
{
struct stat st;
if (fstat (fileno (input_f), &st) == 0 && st.st_size!=0) {
mm_size = st.st_size;
mm_addr = mmap (0, mm_size, PROT_READ,
MAP_SHARED, fileno (input_f), 0);
if ((caddr_t) mm_addr == (caddr_t) - 1)
mm_addr = NULL;
else {
fclose (input_f);
input_f = NULL;
}
}
}
#endif
if (play_with_sigint)
signal (SIGINT, onintr);
Lrxpos = 0;
junkcount = 0;
Beenhereb4 = 0;
somemore:
if (setjmp (intrjmp)) {
if (play_with_sigint)
signal (SIGINT, onintr);
waitack:
junkcount = 0;
c = getinsync (zi, 0);
gotack:
switch (c) {
default:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
DO_SYSLOG((LOG_INFO, "%s/%s: got %d",
shortname, protname(), c));
return ERROR;
case ZCAN:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
DO_SYSLOG((LOG_INFO, "%s/%s: got ZCAN",
shortname, protname()));
return ERROR;
case ZSKIP:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
DO_SYSLOG((LOG_INFO, "%s/%s: got ZSKIP",
shortname, protname()));
return ZSKIP;
case ZACK:
case ZRPOS:
break;
case ZRINIT:
return OK;
}
#ifdef READCHECK
/*
* If the reverse channel can be tested for data,
* this logic may be used to detect error packets
* sent by the receiver, in place of setjmp/longjmp
* rdchk(fdes) returns non 0 if a character is available
*/
while (rdchk (io_mode_fd)) {
#ifdef READCHECK_READS
switch (checked)
#else
switch (READLINE_PF (1))
#endif
{
case CAN:
case ZPAD:
c = getinsync (zi, 1);
goto gotack;
case XOFF: /* Wait a while for an XON */
case XOFF | 0200:
READLINE_PF (100);
}
}
#endif
}
newcnt = Rxbuflen;
Txwcnt = 0;
stohdr (zi->bytes_sent);
zsbhdr (ZDATA, Txhdr);
do {
size_t n;
int e;
unsigned old = blklen;
blklen = calc_blklen (total_sent);
total_sent += blklen + OVERHEAD;
if (Verbose > 2 && blklen != old)
vstringf (_("blklen now %d\n"), blklen);
#ifdef HAVE_MMAP
if (mm_addr) {
if (zi->bytes_sent + blklen < mm_size)
n = blklen;
else {
n = mm_size - zi->bytes_sent;
zi->eof_seen = 1;
}
} else
#endif
n = zfilbuf (zi);
if (zi->eof_seen) {
e = ZCRCE;
if (Verbose>3)
vstring("e=ZCRCE/eof seen");
} else if (junkcount > 3) {
e = ZCRCW;
if (Verbose>3)
vstring("e=ZCRCW/junkcount > 3");
} else if (bytcnt == Lastsync) {
e = ZCRCW;
if (Verbose>3)
vstringf("e=ZCRCW/bytcnt == Lastsync == %ld",
(unsigned long) Lastsync);
#if 0
/* what is this good for? Rxbuflen/newcnt normally are short - so after
* a few KB ZCRCW will be used? (newcnt is never incremented)
*/
} else if (Rxbuflen && (newcnt -= n) <= 0) {
e = ZCRCW;
if (Verbose>3)
vstringf("e=ZCRCW/Rxbuflen(newcnt=%ld,n=%ld)",
(unsigned long) newcnt,(unsigned long) n);
#endif
} else if (Txwindow && (Txwcnt += n) >= Txwspac) {
Txwcnt = 0;
e = ZCRCQ;
if (Verbose>3)
vstring("e=ZCRCQ/Window");
} else {
e = ZCRCG;
if (Verbose>3)
vstring("e=ZCRCG");
}
if ((Verbose > 1 || min_bps || stop_time)
&& (not_printed > (min_bps ? 3 : 7)
|| zi->bytes_sent > last_bps / 2 + last_txpos)) {
int minleft = 0;
int secleft = 0;
time_t now;
last_bps = (zi->bytes_sent / timing (0,&now));
if (last_bps > 0) {
minleft = (zi->bytes_total - zi->bytes_sent) / last_bps / 60;
secleft = ((zi->bytes_total - zi->bytes_sent) / last_bps) % 60;
}
if (min_bps) {
if (low_bps) {
if (last_bps<min_bps) {
if (now-low_bps>=min_bps_time) {
/* too bad */
if (Verbose) {
vstringf(_("zsendfdata: bps rate %ld below min %ld"),
last_bps, min_bps);
vstring("\r\n");
}
DO_SYSLOG((LOG_INFO, "%s/%s: bps rate low: %ld <%ld",
shortname, protname(), last_bps, min_bps));
return ERROR;
}
} else
low_bps=0;
} else if (last_bps < min_bps) {
low_bps=now;
}
}
if (stop_time && now>=stop_time) {
/* too bad */
if (Verbose) {
vstring(_("zsendfdata: reached stop time"));
vstring("\r\n");
}
DO_SYSLOG((LOG_INFO, "%s/%s: reached stop time",
shortname, protname()));
return ERROR;
}
if (Verbose > 1) {
vchar ('\r');
vstringf (_("Bytes Sent:%7ld/%7ld BPS:%-8ld ETA %02d:%02d "),
(long) zi->bytes_sent, (long) zi->bytes_total,
last_bps, minleft, secleft);
}
last_txpos = zi->bytes_sent;
} else if (Verbose)
not_printed++;
ZSDATA (DATAADR, n, e);
bytcnt = zi->bytes_sent += n;
if (e == ZCRCW)
goto waitack;
#ifdef READCHECK
/*
* If the reverse channel can be tested for data,
* this logic may be used to detect error packets
* sent by the receiver, in place of setjmp/longjmp
* rdchk(fdes) returns non 0 if a character is available
*/
fflush (stdout);
while (rdchk (io_mode_fd)) {
#ifdef READCHECK_READS
switch (checked)
#else
switch (READLINE_PF (1))
#endif
{
case CAN:
case ZPAD:
c = getinsync (zi, 1);
if (c == ZACK)
break;
/* zcrce - dinna wanna starta ping-pong game */
ZSDATA (txbuf, 0, ZCRCE);
goto gotack;
case XOFF: /* Wait a while for an XON */
case XOFF | 0200:
READLINE_PF (100);
default:
++junkcount;
}
}
#endif /* READCHECK */
if (Txwindow) {
size_t tcount = 0;
while ((tcount = zi->bytes_sent - Lrxpos) >= Txwindow) {
vfile ("%ld (%ld,%ld) window >= %u", tcount,
(long) zi->bytes_sent, (long) Lrxpos,
Txwindow);
if (e != ZCRCQ)
ZSDATA (txbuf, 0, e = ZCRCQ);
c = getinsync (zi, 1);
if (c != ZACK) {
ZSDATA (txbuf, 0, ZCRCE);
goto gotack;
}
}
vfile ("window = %ld", tcount);
}
} while (!zi->eof_seen);
if (play_with_sigint)
signal (SIGINT, SIG_IGN);
for (;;) {
stohdr (zi->bytes_sent);
zsbhdr (ZEOF, Txhdr);
switch (getinsync (zi, 0)) {
case ZACK:
continue;
case ZRPOS:
goto somemore;
case ZRINIT:
return OK;
case ZSKIP:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
DO_SYSLOG((LOG_INFO, "%s/%s: got ZSKIP",
shortname, protname()));
return c;
default:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
DO_SYSLOG((LOG_INFO, "%s/%s: got %d",
shortname, protname(), c));
return ERROR;
}
}
}
static int
calc_blklen(long total_sent)
{
static long total_bytes=0;
static int calcs_done=0;
static long last_error_count=0;
static int last_blklen=0;
static long last_bytes_per_error=0;
unsigned long best_bytes=0;
long best_size=0;
long this_bytes_per_error;
long d;
unsigned int i;
if (total_bytes==0)
{
/* called from countem */
total_bytes=total_sent;
return 0;
}
/* it's not good to calc blklen too early */
if (calcs_done++ < 5) {
if (error_count && start_blklen >1024)
return last_blklen=1024;
else
last_blklen/=2;
return last_blklen=start_blklen;
}
if (!error_count) {
/* that's fine */
if (start_blklen==max_blklen)
return start_blklen;
this_bytes_per_error=LONG_MAX;
goto calcit;
}
if (error_count!=last_error_count) {
/* the last block was bad. shorten blocks until one block is
* ok. this is because very often many errors come in an
* short period */
if (error_count & 2)
{
last_blklen/=2;
if (last_blklen < 32)
last_blklen = 32;
else if (last_blklen > 512)
last_blklen=512;
if (Verbose > 3)
vstringf(_("calc_blklen: reduced to %d due to error\n"),
last_blklen);
}
last_error_count=error_count;
last_bytes_per_error=0; /* force recalc */
return last_blklen;
}
this_bytes_per_error=total_sent / error_count;
/* we do not get told about every error, because
* there may be more than one error per failed block.
* but one the other hand some errors are reported more
* than once: If a modem buffers more than one block we
* get at least two ZRPOS for the same position in case
* *one* block has to be resent.
* so don't do this:
* this_bytes_per_error/=2;
*/
/* there has to be a margin */
if (this_bytes_per_error<100)
this_bytes_per_error=100;
/* be nice to the poor machine and do the complicated things not
* too often
*/
if (last_bytes_per_error>this_bytes_per_error)
d=last_bytes_per_error-this_bytes_per_error;
else
d=this_bytes_per_error-last_bytes_per_error;
if (d<4)
{
if (Verbose > 3)
{
vstringf(_("calc_blklen: returned old value %d due to low bpe diff\n"),
last_blklen);
vstringf(_("calc_blklen: old %ld, new %ld, d %ld\n"),
last_bytes_per_error,this_bytes_per_error,d );
}
return last_blklen;
}
last_bytes_per_error=this_bytes_per_error;
calcit:
if (Verbose > 3)
vstringf(_("calc_blklen: calc total_bytes=%ld, bpe=%ld, ec=%ld\n"),
total_bytes,this_bytes_per_error,(long) error_count);
for (i=32;i<=max_blklen;i*=2) {
long ok; /* some many ok blocks do we need */
long failed; /* and that's the number of blocks not transmitted ok */
unsigned long transmitted;
ok=total_bytes / i + 1;
failed=((long) i + OVERHEAD) * ok / this_bytes_per_error;
transmitted=total_bytes + ok * OVERHEAD
+ failed * ((long) i+OVERHEAD+OVER_ERR);
if (Verbose > 4)
vstringf(_("calc_blklen: blklen %d, ok %ld, failed %ld -> %lu\n"),
i,ok,failed,transmitted);
if (transmitted < best_bytes || !best_bytes)
{
best_bytes=transmitted;
best_size=i;
}
}
if (best_size > 2*last_blklen)
best_size=2*last_blklen;
last_blklen=best_size;
if (Verbose > 3)
vstringf(_("calc_blklen: returned %d as best\n"),
last_blklen);
return last_blklen;
}
/*
* Respond to receiver's complaint, get back in sync with receiver
*/
static int
getinsync(struct zm_fileinfo *zi, int flag)
{
size_t rxpos;
for (;;) {
int gotblock;
gotblock = zgethdr(Rxhdr, 0, &rxpos);
switch (gotblock) {
case ZCAN:
case ZABORT:
case ZFIN:
case TIMEOUT:
return ERROR;
case ZRPOS:
/* ************************************* */
/* If sending to a buffered modem, you */
/* might send a break at this point to */
/* dump the modem's buffer. */
if (input_f)
clearerr(input_f); /* In case file EOF seen */
#ifdef HAVE_MMAP
if (!mm_addr)
#endif
if (fseek(input_f, (long) rxpos, 0))
return ERROR;
zi->eof_seen = 0;
bytcnt = Lrxpos = zi->bytes_sent = rxpos;
if (Lastsync == rxpos) {
error_count++;
}
Lastsync = rxpos;
return ZRPOS;
case ZACK:
Lrxpos = rxpos;
if (flag || zi->bytes_sent == rxpos)
return ZACK;
continue;
case ZRINIT:
case ZSKIP:
if (input_f) {
fclose (input_f);
input_f=NULL;
}
#ifdef HAVE_MMAP
else if (mm_addr) {
munmap(mm_addr,mm_size);
mm_addr=NULL;
}
#endif
return ZSKIP;
case ERROR:
default:
error_count++;
zsbhdr(ZNAK, Txhdr);
continue;
}
}
}
/* Say "bibi" to the receiver, try to do it cleanly */
static void
saybibi(void)
{
for (;;) {
stohdr(0L); /* CAF Was zsbhdr - minor change */
zshhdr(ZFIN, Txhdr); /* to make debugging easier */
switch (zgethdr(Rxhdr, 0,NULL)) {
case ZFIN:
sendline('O');
sendline('O');
flushmo();
case ZCAN:
case TIMEOUT:
return;
}
}
}
/* Send command and related info */
static int
zsendcmd(const char *buf, size_t blen)
{
int c;
pid_t cmdnum;
size_t rxpos;
cmdnum = getpid();
errors = 0;
for (;;) {
stohdr((size_t) cmdnum);
Txhdr[ZF0] = Cmdack1;
zsbhdr(ZCOMMAND, Txhdr);
ZSDATA(buf, blen, ZCRCW);
listen:
Rxtimeout = 100; /* Ten second wait for resp. */
c = zgethdr(Rxhdr, 1, &rxpos);
switch (c) {
case ZRINIT:
goto listen; /* CAF 8-21-87 */
case ERROR:
case TIMEOUT:
if (++errors > Cmdtries)
return ERROR;
continue;
case ZCAN:
case ZABORT:
case ZFIN:
case ZSKIP:
case ZRPOS:
return ERROR;
default:
if (++errors > 20)
return ERROR;
continue;
case ZCOMPL:
Exitcode = rxpos;
saybibi();
return OK;
case ZRQINIT:
vfile("******** RZ *******");
system("rz");
vfile("******** SZ *******");
goto listen;
}
}
}
/*
* If called as lsb use YMODEM protocol
*/
static void
chkinvok (const char *s)
{
const char *p;
p = s;
while (*p == '-')
s = ++p;
while (*p)
if (*p++ == '/')
s = p;
if (*s == 'v') {
Verbose = 1;
++s;
}
program_name = s;
if (*s == 'l')
s++; /* lsz -> sz */
protocol = ZM_ZMODEM;
if (s[0] == 's' && s[1] == 'x')
protocol = ZM_XMODEM;
if (s[0] == 's' && (s[1] == 'b' || s[1] == 'y')) {
protocol = ZM_YMODEM;
}
}
static void
countem (int argc, char **argv)
{
struct stat f;
for (Totalleft = 0, Filesleft = 0; --argc >= 0; ++argv) {
f.st_size = -1;
if (Verbose > 2) {
vstringf ("\nCountem: %03d %s ", argc, *argv);
}
if (access (*argv, R_OK) >= 0 && stat (*argv, &f) >= 0) {
#if defined(S_ISDIR)
if (!S_ISDIR(f.st_mode) && !S_ISBLK(f.st_mode)) {
#else
int c;
c = f.st_mode & S_IFMT;
if (c != S_IFDIR && c != S_IFBLK) {
#endif
++Filesleft;
Totalleft += f.st_size;
}
} else if (strcmp (*argv, "-") == 0) {
++Filesleft;
Totalleft += DEFBYTL;
}
if (Verbose > 2)
vstringf (" %ld", (long) f.st_size);
}
if (Verbose > 2)
vstringf (_("\ncountem: Total %d %ld\n"),
Filesleft, Totalleft);
calc_blklen (Totalleft);
}
/* End of lsz.c */